Is this an Array of Object BUG? Ionic v6 React - reactjs

As the image shows, it only renders one array of objects.
How to reproduce:
Create a Blank Template and paste this code on ExploreContainer.tsx:
import {
IonCard,
IonCardHeader,
IonCardSubtitle,
IonCardTitle,
IonCardContent,
} from '#ionic/react'
import { useEffect, useState } from 'react'
import './ExploreContainer.css'
interface ContainerProps {}
interface TestArrayObject {
key: string
id: string
name: string
age: number
}
const ExploreContainer: React.FC<ContainerProps> = () => {
const [testArray, setTestArray] = useState<TestArrayObject[]>([])
const arraySample: TestArrayObject[] = [
{
key: '1',
id: '12345',
name: 'Jack',
age: 40,
},
{
key: '2',
id: '67890',
name: 'Black',
age: 30,
},
]
useEffect(() => {
arraySample.map((arr: TestArrayObject) => {
setTestArray([...testArray, arr])
})
}, [])
const listArray = testArray.map((arr) => {
return (
<IonCard>
<IonCardHeader>
<IonCardSubtitle>{arr.id}</IonCardSubtitle>
<IonCardTitle>{arr.name}</IonCardTitle>
</IonCardHeader>
<IonCardContent>
Keep close to Nature's heart... {arr.age}
</IonCardContent>
</IonCard>
)
})
return <>{ listArray }</>
}
export default ExploreContainer
I'm trying to figure out the solution, but happens that i`m more than 24hours trying to figure out and nothing. could someone help?

Related

React state not updating on second index

I have nested objects as described below and updating states.
`
interface BookState {
name: string
authors: AuthorState[]
}
interface AuthorState {
name: string
}
const [bookValues, setBookValues] = useState<BookState[]>(bookStateInitial)
// Add new empty author; which will later be filled from textfields
const onClickAddAuthor = (bookIndex: number) => {
let newAuthor = { } as AuthorState
let authors = [...bookValues[bookIndex].authors, newAuthor]
let newBookState = update(bookValues, { [bookIndex]: { authors: { $set: authors } } })
setBookValues(newBookState) // ** edited
}
// somewhere i populate bookValues as:
bookValues = [
{name: "Book-1", authors: [{name: "Author-1"}] },
{name: "Book-2", authors: [{name: "Author-1"}, {name: "Author-2"}]}
]
`
When I add an author, suppose in "Book-1" index 0, I call the onClickAddAuthor(0), the state updates and UI updates. But when I add an author, suppose in "Book-2" index 1, i call the onClickAddAuthor(1), the state value can be seen updating when printing to console but the UI does not update. I am using https://github.com/kolodny/immutability-helper to update the state.
I expect to add a new empty author on index-1 as well, which should update the state and UI. I tried making deep copies of the book Values and updating the state with that, but it is not working. If it is working in index 0, it should work on other indexes (1, 2, 3 .. ) as well. I am not able to understand.
I tested the posted code with 4 items in bookValues, it seems that the onClickAddAuthor is working as expected. Perhaps the output logic could be checked to see if it updates correctly.
Simple test demo on: stackblitz
import { useState } from 'react';
import './App.css';
import update from 'immutability-helper';
interface AuthorState {
name: string;
}
interface BookState {
name: string;
authors: AuthorState[];
}
const bookStateInitial = [
{ name: 'Book-1', authors: [{ name: 'Author-1' }] },
{ name: 'Book-2', authors: [{ name: 'Author-1' }, { name: 'Author-2' }] },
{ name: 'Book-3', authors: [{ name: 'Author-1' }] },
{ name: 'Book-4', authors: [{ name: 'Author-1' }, { name: 'Author-2' }] },
];
function App() {
const [bookValues, setBookValues] = useState<BookState[]>(bookStateInitial);
const onClickAddAuthor = (bookIndex: number) => {
let newAuthor = { name: 'Test Author' } as AuthorState;
let authors = [...bookValues[bookIndex].authors, newAuthor];
let newBookState = update(bookValues, {
[bookIndex]: { authors: { $set: authors } },
});
setBookValues(newBookState);
};
return (
<main className="App">
<section>
{[0, 1, 2, 3].map((item) => (
<button key={item} onClick={() => onClickAddAuthor(item)}>
{`Test: add author for Book-${item + 1}`}
</button>
))}
</section>
<ul>
{bookValues.map((book) => (
<li key={book.name}>
{`name: ${book.name}, authors: ${book.authors
.map((author) => author.name)
.join(', ')}`}
</li>
))}
</ul>
</main>
);
}
export default App;

Reactjs map over redux store

I am trying to map over the items i am receiving from my API and are in my store
this is how the data look like in the state:
{
vessels: {
vessels: [
{
id: 1,
component_count: 3,
inventory_item_count: 1,
name: 'Aylah',
imo: 'Aylah123',
image: 'http://127.0.0.1:8000/media/vessel_image/aylah.jpg'
},
{
id: 2,
component_count: 1,
inventory_item_count: 0,
name: 'Sinaa',
imo: 'sinaa123',
image: 'http://127.0.0.1:8000/media/vessel_image/DSCF9831.jpg'
},
{
id: 3,
component_count: 0,
inventory_item_count: 0,
name: 'Amman',
imo: 'amman123',
image: 'http://127.0.0.1:8000/media/vessel_image/amman.jpg'
},
{
id: 4,
component_count: 0,
inventory_item_count: 0,
name: 'Queen',
imo: 'Queen 123',
image: 'http://127.0.0.1:8000/media/vessel_image/1.jpg'
}
]
}
}
i am just trying to use the map function but i cant seem to get it work
here i am trying to print all the names of the vessels :
OverviewFleet.js:
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { fetchVessels } from "../../../features/vesselSlice";
import VesselCard from "./VesselCard";
function OveriewFleet() {
const vessels = useSelector((state) => state.vessels);
const dispatch = useDispatch();
useEffect(() => {
fetchVessels()(dispatch);
}, [dispatch]);
return (
<div className="fleet-overview">
<div className="fleet-overview-container">
<span className="fleet-overview-heading-title">Fleet overview :</span>
<div className="fleet-overview-cards">
{vessels.map((vessel) => ({ vessel.name }))}
</div>
</div>
</div>
);
}
export default OveriewFleet;
Looks like you are having object inside vessels with name vessels.
try with vessels?.vessels?.map
{vessels?.vessels?.map((vessel) => ({ vessel.name }))}

Testing zustand state changes caused by a component in Jest

I am pretty new to using jest and Im trying to test a component that makes a state change which acts upon my global state (using Zustand). Basically im clicking a button and its adding an item to my state.traits. Here is my component code:
import { Flex, useToast } from '#chakra-ui/react'
import { FC } from 'react'
import { useProfileStore } from 'stores/profileStore'
interface DataTrait {
name: string,
id: string
}
type Props = {
trait: DataTrait
}
export const ChipItem: FC<Props> = ({ trait }) => {
const { traits, setTraits } = useProfileStore()
const toast = useToast()
const traitNames = traits.map((trait) => trait.name)
const emptyTraits = traits.filter((trait) => trait.name === "")
const handleClick = (trait: DataTrait) => {
if (!traitNames.includes(trait.name) && emptyTraits.length !== 0) {
let currentItem = traits.filter(trait => trait.name === "")[0]
let items = [...traits]
let item = {position: currentItem.position, id: trait.id, name: trait.name}
items[items.indexOf(currentItem)] = item
setTraits(items)
} else if (emptyTraits.length === 0){
toast({
title: 'Error',
status: 'error',
description: 'Only 5 traits can be selected',
isClosable: true,
duration: 5000
})
} else {
toast({
title: 'Error',
status: 'error',
description: 'Please select unique traits',
isClosable: true,
duration: 5000
})
}
}
return (
traitNames.includes(trait.name) ? (
<Flex mx={4} p={2} cursor="pointer" borderRadius="20px" backgroundColor="green" borderWidth="1px" borderColor="white" textColor="white" onClick={() => handleClick(trait)}>{trait.name}</Flex>
) : (
<Flex mx={4} p={2} cursor="pointer" borderRadius="20px" borderWidth="1px" borderColor="grey" onClick={() => handleClick(trait)}>{trait.name}</Flex>
)
)
}
here is my store code:
import create from 'zustand'
export interface Trait {
position: string,
name: string,
id: string,
}
export type Traits = Trait[]
const initialTraits = [
{position: "0", name: "", id: ""},
{position: "1", name: "", id: ""},
{position: "2", name: "", id: ""},
{position: "3", name: "", id: ""},
{position: "4", name: "", id: ""},
]
export type ProfileStore = {
traits: Traits;
setTraits: (traits: Traits) => void;
clearTraits: () => void;
}
export const useProfileStore = create<ProfileStore>((set) => ({
traits: initialTraits,
setTraits: (traits) => set({ traits }),
clearTraits: () => set({ traits: initialTraits })
}))
and here is my test code:
import React from 'react';
import { ChipItem } from "../../ChipList/ChipItem";
import { act, render, renderHook } from "#testing-library/react";
import { useProfileStore } from "../../../stores/profileStore";
const stubbedTrait = {
name: "Doing Work",
id: "efepofkwpeok"
}
it("displays the trait chip", () => {
const { queryByText } = render(<ChipItem trait={stubbedTrait} />);
expect(queryByText("Doing Work")).toBeTruthy();
})
it("sets the chip information in the store", () => {
act(() => {
const { traits } = renderHook(() => useProfileStore())
const { getByText } = render(<ChipItem trait={stubbedTrait}/>);
getByText(stubbedTrait.name).click()
expect(traits.includes(stubbedTrait)).toBeTruthy()
})
})
whats happening, is that it keeps telling me that renderHook is not a function and traits always comes back undefined. any help would be greatly appreciated!
Currently you must install and import React Testing Hooks separately
The best way to unit test Zustand state changes inside and specific component is not by using Zustand but by mocking the store hook with Jest.
You should create a test case for the Zustand Store using React Hook Testing library and once you verify the hook behaves as expected, then you mock the store with manual traits and setTraits changes.
Once you have the unit tests then you should test the behaviour of the real hook and components together with integration tests.

DetailsList is getting re-rendered and hence is loosing its state

So I have been trying to accomplish the idea that whenever the user clicks any row in the DetailsList the Button gets enabled and when the user clicks outside the selectionzone the Button gets disabled.
This is my code
import { DetailsList, SelectionMode, Selection, ISelection, initializeIcons, PrimaryButton } from '#fluentui/react'
import {useMemo } from 'react'
import { useBoolean } from '#uifabric/react-hooks'
interface ICurrency {
type: string,
amount: number
}
function App() {
initializeIcons()
const [isBtn, { setTrue: disableBtn, setFalse: enableBtn }] = useBoolean(true)
const items: ICurrency[] = [
{
type: 'INR',
amount: 20
},
{
type: 'USD',
amount: 50
},
{
type: 'GBP',
amount: 70
}
]
const selection: ISelection = useMemo(() => new Selection(
{
onSelectionChanged: ()=>{
if(selection.getSelectedCount() > 0){
enableBtn()
}else{
disableBtn()
}
}
}
), [items])
return (
<div className="App">
<PrimaryButton text="Button" disabled={isBtn}/>
<DetailsList
items={items} selectionMode={SelectionMode.single}
selection={selection}
/>
</div>
);
}
export default App;
I even used useMemo and kept on banging my head but the problem persists where clicking any row the state is lost and the button is not enabled. I have already tried storing the state of selection also, count, everything but it seems I'm missing out on something essential or fundamental for the implementation
You need to memoize items because on each render it's being re-assigned and considered a new array which causes your selection to change because it relies on items as a useMemo dependency. On each state update the selection will reset.
So one way you can fix this is by moving the items out of the function so that it holds reference instead of creating a new items array on each render.
const items = [
{
type: "INR",
amount: 20
},
{
type: "USD",
amount: 50
},
{
type: "GBP",
amount: 70
}
];
function App() {
// code
}
or by using useMemo on those items:
const items = useMemo(() => [
{
type: "INR",
amount: 20
},
{
type: "USD",
amount: 50
},
{
type: "GBP",
amount: 70
}
],[]);
Also I see you have an error, the initializeIcons should only be called once. So that should probably be placed in useEffect:
useEffect(() => {
initializeIcons();
},[])
The final code sample should look like this:
import {
DetailsList,
SelectionMode,
Selection,
ISelection,
initializeIcons,
PrimaryButton
} from "#fluentui/react";
import { useMemo, useEffect } from "react";
import { useBoolean } from "#uifabric/react-hooks";
const items = [
{
type: "INR",
amount: 20
},
{
type: "USD",
amount: 50
},
{
type: "GBP",
amount: 70
}
];
function App() {
useEffect(() => {
initializeIcons();
}, []);
const [isBtn, { setTrue: disableBtn, setFalse: enableBtn }] = useBoolean(
true
);
const selection = useMemo(
() =>
new Selection({
onSelectionChanged: () => {
if (selection.getSelectedCount() > 0) {
enableBtn();
} else {
disableBtn();
}
}
}),
[items]
);
return (
<div className="App">
<PrimaryButton text="Button" disabled={isBtn} />
<DetailsList
items={items}
selectionMode={SelectionMode.single}
selection={selection}
/>
</div>
);
}
export default App;
The Accepted answer has the proper reasoning for the issue. I just wanted to post my solution too. I just had to store the state of the items
import { DetailsList, SelectionMode, Selection, initializeIcons, PrimaryButton } from '#fluentui/react'
import { useEffect, useState } from 'react'
import { useBoolean } from '#uifabric/react-hooks'
interface ICurrency {
type: string,
amount: number
}
function App() {
useEffect(() => {
initializeIcons();
}, []);
const [isBtn, { setTrue: disableBtn, setFalse: enableBtn }] = useBoolean(true)
let _selection = new Selection({
onSelectionChanged: () => {
if (_selection.getSelectedCount() > 0) {
enableBtn()
} else {
disableBtn()
}
}
});
let _initialItems: ICurrency[] = [
{
type: 'INR',
amount: 20
},
{
type: 'USD',
amount: 50
},
{
type: 'GBP',
amount: 70
}
]
const [items, setItems] = useState(_initialItems)
return (
<>
<PrimaryButton text="Button" disabled={isBtn} />
<DetailsList
items={items}
selection={_selection}
selectionMode={SelectionMode.single}
/>
</>
);
}
export default App;
Now say if the items are coming from some props or some state management, then just use setItems inside a useEffect and set the dependency as that source

react material-ui mui-datatables onRowSelectionChange ID

This is my react code. I am using the material UI. I am working with ID related events. the full code is provided below.
Here, the index ID is getting automatically generated. The issue has to do with that.
import React, { useState } from "react";
import ReactDOM from "react-dom";
import MUIDataTable from "mui-datatables";
import InputLabel from "#material-ui/core/InputLabel";
import MenuItem from "#material-ui/core/MenuItem";
import FormHelperText from "#material-ui/core/FormHelperText";
import FormControl from "#material-ui/core/FormControl";
import Select from "#material-ui/core/Select";
function Ag() {
const [responsive, setResponsive] = useState("vertical");
const onCellClick = () => {
console.log("sadf");
};
const onRowsDelete = () => {
console.log("remove");
};
const onRowSelectionChange = (ev, ex, ez) => {
console.log(ez);
};
const columns = ["Name", "Title", "Location"];
const options = {
filter: true,
filterType: "dropdown",
responsive,
onCellClick,
onRowsDelete,
onRowSelectionChange,
};
const data = [
{
Id: "1",
Name: "sunder",
Title: "dlamds",
Location: "asdfsa",
},
{
Id: "2",
Name: "cvzx",
Title: "sadfsda",
Location: "sadfsdacv",
},
{
Id: "3",
Name: "dsfas",
Title: "werq",
Location: "ewqrwqe",
},
{
Id: "4",
Name: "wqer",
Title: "gfdsg",
Location: "bvcxb",
},
{
Id: "5",
Name: "ereq",
Title: "qwer",
Location: "sdafas",
},
];
return (
<React.Fragment>
<MUIDataTable
title={"ACME Employee list"}
data={data}
columns={columns}
options={options}
/>
</React.Fragment>
);
}
export default Ag;
I want to get a data ID instead of an index ID that was automatically generated when I clicked.
What should I do?
onRowSelectionChange: (currentSelect, allSelected) => {
const result = allSelected.map(item => { return data.at(item.index) });
const selectedIds = result.map(item => {
return item.id;
});
console.log(selectedIds);
}

Resources