react-beautiful-dnd: destination is equal to null - reactjs

Here is the thing. I'm learning to React beautiful dnd in by building a todo app. And I have set up the app as far as styling the component and writing the logic to display the todo cards.
The problem is this, as I drag the card in todo to progress or done, the property "destination", in an object logged in the console (check the code to see where it's logged) destination is equal to null. It's supposed to be equal to an object. What seems to be the problem.
The full code is located in the link below:
https://codesandbox.io/s/async-fast-hziot?file=/src/App.js
Preview:
import "./styles.css";
import { useState } from "react";
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';
export default function App() {
const [populateStore, setPopulateStore] = useState(
{
todo: {
_id:"todo",
data: [
{
_id: 'ms7690fvs6sd53h328sof-0sdf',
author: 'anonymous',
title: 'Going to the pack',
date: '11th, January 2022'
},
{
_id: 'sdfdf32gf98tddswjk73i2r',
author: 'anonymous',
title: 'Visit my parents for the holiday',
date: '11th, January 2022'
}
]},
inprogress: {
_id: "in progress",
data:[]},
done: {
_id:"done",
data: []
}
}
);
function handleOnDragEnd(result) {
console.log(result, "result")
}
return (
<div className="populate-container">
{Object.entries(populateStore).map(([title, array],index) => {
return (
<div className="todo-container" key={index}>
<div className="todo-headings">
{title}
</div>
<DragDropContext onDragEnd={handleOnDragEnd}>
<Droppable
droppableId={`${array._id}`}
type={`${array._id}`}
>
{
(provided) => {
return (
<div className="_droppable" {...provided.droppableProps} ref={provided.innerRef}>
{
array.data.map((item, id) => {
return (
<Draggable key={id} draggableId={`${id}`} index={id}>
{
(provided) => {
return (
<div className="card" ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps} key={id}>
<div className="card-description">
<h5>{item.title}</h5>
<p>
{
item.date
}
</p>
</div>
</div>
)
}
}
</Draggable>
)
})
}
{provided.placeholder}
</div>
)
}
}
</Droppable>
</DragDropContext>
</div>
)
})}
</div>
);
}

The problem was two things.
The tag "DragDropContext" was meant to be the first tag before mapping out the stored data in the state "populate hook". But instead, I was mapping out the data first with DragDropContext being a child node.
This is the right way:
<DragDropContext onDragEnd={handleOnDragEnd}>
{Object.entries(populateStore).map(([title, array],index) => { ......
Not this:
{Object.entries(populateStore).map(([title, array],index) => { ......
<DragDropContext onDragEnd={handleOnDragEnd}>
Then the second was an unseen problem not asked initially at the question section and that is the id of the Droppaple and the draggable. Always make sure the ids of these tags are in strings, not numbers, and make sure the ids are not alike for example the problem in my code, the id of the draggable card in the "todo", "progress" and "done" columns were both the number 1 (I manually added an object to the "done" array located in the state to test it, and I was using the index in the higher-order function, "map" as an id in the draggable tag), so whenever I try to pick up the first one, in the "todo" column I will end up carrying the first one in the "done" column. So I resolved that by using the string id in the object.
I used this:
id: _id: "ms7690fvs6sd53h328sof-0sdf" in <Draggable key={id}
Not this anymore:
array.data.map((item, id) => { in <Draggable key={id}
when id = 1 or 2 or 3 and so on.
You can view the solution in the javascript file "answer_code.js" in the link provided in the question section.

Related

Expression statement is not assignment or call when looping through a list and trying to create a grid layout of components

I am trying to create a grid layout of video components but my IDE gives me a warning saying
Expression statement is not assignment or call
import React, {Fragment} from 'react';
import VideoClip from "../Video/VideoClip";
function SubjectView(props) {
let ids = ["RfKHsvF69VdjdMu6bdugsyRcjYpQXrpKd6iZHeEknCkY00",
"RfKHsvF69VdjdMu6bdugsyRcjYpQXrpKd2ipHeEknCkY00",
"RfKHsvF69Vdjdiu6bdugsyRcjYpQXrpKd2iZHeEknCkY00"
];
return (
<Fragment>
<div className="columns-3" >
{ids.map((id)=>{
<VideoClip id={id}/>
console.log(id)
})}
</div>
</Fragment>
);
}
export default SubjectView;
I see the IDs printed in the console but nothing renders.
The video component looks like
function VideoClip() {
let { id } = useParams();
return (
<div className="container mx-auto px-4">
<MuxPlayer
streamType="on-demand"
playbackId={id}
metadata={{
video_id: "video-id-54321",
video_title: "Test video title",
viewer_user_id: "user-id-007",
}}
/>
</div>
);
}
export default VideoClip
I am wondering if I am trying to create the components incorrectly. Is there a best practice when trying to achieve this?
You're not returning any value from ids.map
<>
<div className="columns-3" >
{ids.map((id)=><VideoClip id={id}/>)}
</div>
</>
One issue is you are not returning <VideoClip id={id}/> in map function in jsx. Also, if map is used - key needs to be set
return (
<Fragment>
<div className="columns-3">
{ids.map((id) => {
console.log(id);
return <VideoClip key={id} id={id} />;
})}
</div>
</Fragment>
);
Next issue is about VideoClip component parameter. Id needs to be extracted in that way in case it is passed as an attribute. Also, memoize the objects that you are passing down to a components. Metadata here, for example.
function VideoClip({ id }) {
const metadata = useMemo(() => {
return {
video_id: "video-id-54321",
video_title: "Test video title",
viewer_user_id: "user-id-007"
};
}, []);
return (
<div className="container mx-auto px-4">
<MuxPlayer streamType="on-demand" playbackId={id} metadata={metadata} />
</div>
);
}
Last thing - wrap your array in useMemo so this array will not cause crazy rerenders.
const ids = useMemo(
() => [
"RfKHsvF69VdjdMu6bdugsyRcjYpQXrpKd6iZHeEknCkY00",
"RfKHsvF69VdjdMu6bdugsyRcjYpQXrpKd2ipHeEknCkY00",
"RfKHsvF69Vdjdiu6bdugsyRcjYpQXrpKd2iZHeEknCkY00"
],
[]
);
Note: you will see x2 logs in the console in the Codesandbox due to <StrictMode> is set.

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.

Possible to render components in reverse manner in JSX?

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

Testing-librairy : How to check a text when it is returned by a function

I have a react component which displays a text of which some words may change depending on the value of the object
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Card, Col, Row } from 'react-bootstrap';
function RenderPressMedia(props: any) {
const { t } = useTranslation();
const [pressPlanned, setPressPlanned] = useState<any[]>([]);
useEffect(() => {
if (!props.pressPlannedData) return;
setPressPlanned(props.pressPlannedData);
}, [props.pressPlannedData]);
const renderMedia = (media: string) => {
switch (media) {
case 'PHONE':
return t('press.media.phone');
case 'LETTER':
return t('press.media.letter');
case 'EMAIL':
return t('press.media.email');
case 'SMS':
return t('press.media.sms');
}
};
const renderPress = (media: string) => {
return (
<>
{t(`press.text`, {
media: renderMedia(media),
})}
</>
);
};
return (
<Row>
{pressPlanned.length > 0 &&
pressPlanned.map((press, index) => (
<Col lg={12} className="col-main" key={index}>
<Card>
<Card.Body>
<ul className="d-flex flex-row m-0 list-unstyled align-items-center">
<li aria-label="Reminder to do or reminder for today">
{renderPress(press.media)}
</li>
</ul>
</Card.Body>
</Card>
</Col>
))}
</Row>
);
}
export default RenderPressMedia;
renderPressMedia function returns a translation which will change depending on the variable sent.
Component that works very well.
But when I want to perform a test on this component with testing-library, I cannot verify the content returned by the pressMedia function.
Here is the test carried out.
import React from 'react';
import { render, screen } from '#testing-library/react';
import RenderPressMedia from '../render-press-media';
const data: any[] = [
{
id: 65,
media: 'LETTER',
firstPlannedDate: '2021-09-03',
plannedDate: '2021-09-03',
comment: 'autogenerated',
createdDate: '2021-08-27T09:43:52',
lastModifiedDate: '2021-08-27T09:43:52',
},
];
describe('<RenderPressMedia/>', () => {
it('should display an render press media with data', () => {
//given
render(<RenderPressMedia pressPlannedData={data} />);
//then
expect(screen.getByText(/letter/i)).toBeInTheDocument();
});
});
The test consists in verifying the presence of the word 'letter' returned by the pressMedia function
expect(screen.getByText(/letter/i)).toBeInTheDocument();
But unfortunately I got an error message
Unable to find an element with the text: /courrier/i. This could be because the
text is broken up by multiple elements. In this case, you can provide a function
for your text matcher to make your matcher more flexible.
Ignored nodes: comments, <script />, <style />
<body>
<div>
<div
class="row"
>
<div
class="col-main col-lg-12"
>
<div
class="card"
>
<div
class="card-body"
>
<ul
class="d-flex flex-row m-0 list-unstyled align-items-center"
>
<li
aria-label="Reminder to do or reminder for today"
>
press.text
</li>
</ul>
</div>
</div>
</div>
</div>
TestingLibraryElementError: Unable to find an element with the text: /courrier/i.
This could be because the text is broken up by multiple elements. In this case, you
can provide a function for your text matcher to make your matcher more flexible.
the pressMedia function in testingLibrairy does not return its contents so impossible to verify the presence of the word letter, return value is press.text
If you have a solution to correct this problem.
It looks like, it's happening because you're using the i18n functions, but also it's mocked and just gives back to you the text which received as a parameter.
Any case, in this case, what you're doing is pretty well. But also you're testing the translation. So, it can be more complicated and hard to maintain.
So, I'd recommend to test if it's using the correct media would be something like:
<li aria-label="Reminder to do or reminder for today" data-testid="component" data-media={press.media}>
{renderPress(press.media)}
</li>
And in the testing part:
expect(screen.getByTestId('component')).toHaveAttribute('data-media', data.media);
In this case, ignoring the language, You know which kind of media has your component.
I created this github repo as example:
Repo: https://github.com/joseglego/test-testing-library/
Component: https://github.com/joseglego/test-testing-library/blob/main/src/RenderPressMedia.js
Test: https://github.com/joseglego/test-testing-library/blob/main/src/RenderPressMedia.test.js
Specific commit: https://github.com/joseglego/test-testing-library/commit/f3c222f92313c909c8d4a1f359daf01bdd0f880d
Basically, that test is running on my local.

react.js buttons that display different items from array

I didn't know how to ask this but basically I just want list of buttons that display different text. When you click a button it shows the text. But instead of hardcoding every button I want it to be dynamic. So far when I click any of my buttons, everything shows instead of individually. (one button should show one definition" I think it is the way I am using state probably.
import React, { useState } from "react";
import terms from "./TermsComponentData.js";
import "./TermsComponent.css";
function TermsComponent() {
const [showTerm, setShowTerm] = useState(false);
const buttonList = terms.map((term, index) => (
<div>
{showTerm && <h1>{term.definition}</h1>}
<button
key={index}
className="buttons-container-button"
onClick={() => {
setShowTerm(!showTerm);
}}
>
{term.name}
</button>
</div>
));
return (
<div className="terms-container">
<div className="buttons-container">{buttonList}</div>
</div>
);
}
MY DATA
const terms = [
{
name: "Term1",
definition: "This is the definition1",
},
{
name: "Term2",
definition: "This is the definition2",
},
{
name: "Term3",
definition: "This is the definition 3",
},
{
name: "Term4",
definition: "This is the definition 4",
}
];
export default terms;
Currently your state variable showTerm is common for all of the buttons. So to achieve the result you are describing, you need to encapsulate the state for each button.
The easiest way to do this, is to create a separate component which contains the showTerm state, and then map a list of this component with the given data based on the terms:
// 👇 added separate term button with it's own state
function TermButton({ term }) {
const [showTerm, setShowTerm] = useState(false);
return (
<div>
{showTerm && <h1>{term.definition}</h1>}
<button
className="buttons-container-button"
onClick={() => {
setShowTerm(!showTerm);
}}
>
{term.name}
</button>
</div>
);
}
function TermsComponent() {
return (
<div className="terms-container">
<div className="buttons-container">
{ // 👇 for each term, create a TermButton with the given term
terms.map((term, index) => <TermButton key={index} term={term} />)}
</div>
</div>
);
}

Resources