React hooks accordion toggles all blocks instead of one - reactjs

With a React Accordion I wanted to create a function that will only show the information of the belonging episode. But now it seems that all blocks are doing the same thing. How can I build that function so one block will be toggling instead of all?
Link to CodeSandbox
export const HomePage: React.FC<IProps> = () => {
const [open, setOpen] = useState(false);
return (
<div>{data.characters.results.map((character: { id: number, name: string, image: string; episode: any; }, index: number) => (
<div key={character.id}>
<CharacterHeading>{character.name}</CharacterHeading>
<CharacterImage src={character.image} alt={character.name} />
{character.episode.map((char: { name: string; air_date: string; episode: string; characters: any, id: number; }) => (
<div key={char.id}>
{char.name}
{char.air_date}
{char.episode}
<AccordionButton open={open}
onClick={() => setOpen(!open)}>
Check all characters
</AccordionButton>
<EpisodeListSection open={open}>
{char.characters.map((ep: { id: number, name: string; image: string; }, index: number) => (
<EpisodeInfo key={ep.id}>
<EpisodeInfoBlock>
<EpisodeName>{ep.name}</EpisodeName>
<EpisodeImage src={ep.image} alt={ep.name} />
</EpisodeInfoBlock>
</EpisodeInfo>
))}
</EpisodeListSection>
</div>
))}
</div>
))
}</div>
);
};

You only have one open variable that you are passing to every accordion. Thus, when one accordion changes that value with setOpen, it changes for all of them.
If AccordionButton is a component you built, I would suggest letting that component handle its own open/closed state. Your parent doesn't seem like it needs to control that. Otherwise, you will need open to be an array of values, you will need to only pass the correct open value to each accordion, and you will need to have your onClick function tell the parent which open value it needs to change.
Again, that seems like a lot of work for not a lot of utility. Better to just let the accordions keep track of whether they're open.

Here is the updated code that you can use:
import { useState } from "react";
import { gql, useQuery } from "#apollo/client";
import {
AccordionButton,
CharacterImage,
CharacterHeading,
EpisodeInfo,
EpisodeImage,
EpisodeInfoBlock,
EpisodeListSection,
EpisodeName
} from "./HomePage.styles";
export const GET_CHARACTER = gql`
query {
characters(page: 2, filter: { name: "rick" }) {
results {
name
image
gender
episode {
id
name
air_date
episode
characters {
id
name
image
}
}
}
}
location(id: 1) {
id
}
episodesByIds(ids: [1, 2]) {
id
}
}
`;
export interface Episodes {
name: string;
air_data: string;
episode: string;
characters: Array<any>;
}
export interface IProps {
episodeList?: Episodes[];
}
export const HomePage: React.FC<IProps> = () => {
const { data, loading, error } = useQuery(GET_CHARACTER);
const [inputValue, setInputValue] = useState("");
const [open, setOpen] = useState(false);
const [selectedId, setSelectedId] = useState(0);
const onChangeHandler = (text: string) => {
setInputValue(text);
};
const onSelectItem = (selectedItemId: number) => {
if (selectedId !== selectedItemId) {
setSelectedId(selectedItemId);
} else {
setSelectedId(-1);
}
};
if (loading) return <p>loading</p>;
if (error) return <p>ERROR: {error.message}</p>;
if (!data) return <p>Not found</p>;
return (
<div>
<input
type="text"
name="name"
onChange={(event) => onChangeHandler(event.target.value)}
value={inputValue}
/>
<div>
{data.characters.results.map(
(
character: {
id: number;
name: string;
image: string;
episode: any;
},
index: number
) => (
<div key={character.id}>
<CharacterHeading>{character.name}</CharacterHeading>
<CharacterImage src={character.image} alt={character.name} />
{character.episode.map(
(char: {
name: string;
air_date: string;
episode: string;
characters: any;
id: number;
}) => (
<div key={char.id}>
{char.name}
{char.air_date}
{char.episode}
<AccordionButton
onClick={() => onSelectItem(char.id)}
open={char.id === selectedId ? true : false}
>
Check all characters
</AccordionButton>
<EpisodeListSection
open={char.id === selectedId ? false : true}
>
{char.characters.map(
(
ep: { id: number; name: string; image: string },
index: number
) => (
<EpisodeInfo key={ep.id}>
<EpisodeInfoBlock>
<EpisodeName>{ep.name}</EpisodeName>
<EpisodeImage src={ep.image} alt={ep.name} />
</EpisodeInfoBlock>
</EpisodeInfo>
)
)}
</EpisodeListSection>
</div>
)
)}
</div>
)
)}
</div>
</div>
);
};

Related

Checked prop in MUI switch is not updating

RankPermission.value in switchPermission function is changing from false to true, but MUI Switch is not updating in the browser. I don't know why isn't it updating, and I didn't try much. I don't have any ideas how can I fix it.
const [activeRank, setActiveRank] = useState<FactionRanks>();
export type FactionRanks = {
id: number;
name: string;
rankPermissions: FactionRanksPermissions[];
};
export type FactionRanksPermissions = {
label: string;
value: boolean;
};
const ActionMenu = () => {
const { activeRank } = useContext(FactionPanelContext);
const switchPermission = (rankPermission: FactionRanksPermissions) => {
rankPermission.value = !rankPermission.value;
console.log(rankPermission);
};
return (
<Wrapper>
<Buttons>
{activeRank?.rankPermissions.map(
(rankPermission: FactionRanksPermissions, index: number) => (
<Row key={index}>
<OptionDetails>{rankPermission.label}</OptionDetails>
<IOSSwitch
checked={rankPermission.value}
inputProps={{ 'aria-label': 'secondary checkbox' }}
onClick={() => switchPermission(rankPermission)}
/>
</Row>
)
)}
</Buttons>
</Wrapper>
);
};
You are changing rankPermission in place here
rankPermission.value = !rankPermission.value;
You should be setting state, with a new object of rankPermission, where it is defined instead.

Generic Parent ignores Generic children React

First, here's my "Parent Component" or "Factory", I'm not quite certain what the terminology is.
export interface ReadOnlyProps {
ignoreReadOnly?: boolean;
disabled?: boolean;
}
export const WithReadOnly = <T,>(Component: React.ComponentType<T>): React.ComponentType<T & ReadOnlyProps> => {
const ReadOnlyComponent = (props: T) => {
const { ...rest } = props as T & ReadOnlyProps;
return <Component {...(rest as T)} disabled={true} />;
};
return ReadOnlyComponent;
};
Here are the components I did for this:
const Dropdown = <T,>(props: { items: T[]; onChange: (data: T) => void }) => {
return (
<ul>
{props.items.map((item, index) => {
<li onClick={() => props.onChange(item)}>{index}</li>;
})}
</ul>
);
};
const DropdownReadOnly = WithReadOnly(Dropdown);
Then I did this two examples.
// Works
<Dropdown items={[{ name: 'Someone' }, { name: 'Someone else' }]} onChange={(item) => alert(item.name)} />;
// Doesn't work
<DropdownReadOnly items={[{ name: 'Someone' }, { name: 'Someone else' }]} onChange={(item) => alert(item.name)} />;
The first one is working, the second one is complaining that item is unknown on the onChange prop.
Anyone know what I am doing wrong here?
Thank you beforehand!

how to send function in typescript interface?

// ExpenseForm.tsx
interface ExpenseData {
enteredTitle: string;
enteredAmount: number;
enteredDate: string;
}
const ExpenseForm = (props) => {
const [userInput, setUserInput] = useState<ExpenseData>({
enteredTitle: "",
enteredAmount: 10,
enteredDate: "",
});
const { register, handleSubmit, resetField } = useForm<ExpenseData>();
const onValid = (data: ExpenseData) => {
setUserInput(data);
resetField("enteredAmount");
resetField("enteredDate");
resetField("enteredTitle");
};
};
// NewExpense.tsx
const NewExpense = (props) => {
const saveExpenseDataHandler = (enteredExpenseData) => {
const expenseData = {
...enteredExpenseData,
id: Math.random().toString(),
};
console.log(expenseData); // I want to see this.
props.onAddExpense(expenseData);
};
return (
<div className="new-expense">
<ExpenseForm onSaveExpenseData={saveExpenseDataHandler} />
</div>
);
};
//App.tsx
const App = () => {
const expenses = [
{
id: "xxxx",
title: "xxx",
amount: xxxx,
date: new Date(2022, 5, 16),
},
];
const addExpenseHandler = (expense) => {
console.log(expense);
};
return (
<div>
<NewExpense onAddExpense={addExpenseHandler} />
</div>
);
};
I'm using react with typescript in Udemy Course.
I want to send onSaveExpenseData in NewExpense.tsx to ExpenseForm.tsx.
How do I define type onSaveExpenseData in interface?
Also I want to using reset
I tried to onSaveExpenseData:()=>void, but it doesn't work.
You can export a type for expense.
export interface Expense {
id: string;
title: string;
amount: number;
date: Date;
}
Then in your function you can type expense
const addExpenseHandler = (expense: Expense) => {
console.log(expense);
};
In your NewExpense.tsx you can type its props to this:
interface NewExpenseProps {
onAddExpense: (e: Expense) => void;
}
export const NewExpense = ({ onAddExpense }: NewExpenseProps) => {
const saveExpenseDataHandler = (enteredExpenseData: Expense) => {
const expenseData = {
...enteredExpenseData,
id: Math.random().toString(),
};
console.log(expenseData); // I want to see this.
onAddExpense(expenseData);
};
return (
<div className="new-expense">
<ExpenseForm onSaveExpenseData={saveExpenseDataHandler} />
</div>
);
};
In your expense form you can do the same thing:
interface ExpenseFormProps {
onSaveExpenseData: (v: Expense) => void;
}
export const ExpenseForm = ({ onSaveExpenseData }: ExpenseFormProps) => {
console.log(onSaveExpenseData);
return <div>ExpenseForm</div>;
};
You need to define your props on the react component.
Also the type you wrote was almost correct, but it takes 1 argument that you were missing.
The easiest way to do this is like this:
import { FC } from 'react';
interface Props {
onSaveExpenseData: (_: Expense) => void
}
const ExpenseForm: FC<Props> = ...your current function
FC is the react type for FunctionalComponent. It will define the props and return value for you. The < Props > is a generic you pass to this type. It looks pretty complex but you don't have to fully understand it to use it.

I'm kinda new to React Js and I keep getting "Type Error: Cannot set properties of undefined (setting 'buttonText')"

I couldn't find a solution yet and i'm stuck.
When I click the button "add to cart" it should change the text to "adding to cart..." but Instead i'm getting that error.
I'm not getting why is undefined if Im waiting until I get all the data from the fetch.
I'm getting this object after fetching:
{
"products":[
{ "id":0,
"name":"doughnut choco",
"price":25,
"desc":"Doughnut salado relleno de Kimchi casero y queso crema vegano",
"img":"https://delishvegan.com/wp-content/uploads/2020/11/brunch2-300x300.png",
"units":100,
"buttonText":"add to cart"
},
}
Here's my code:
import React, { useState } from "react";
import "../sass/layout/_grid.scss";
import "../sass/components/_product-card.scss";
import "../sass/components/_buttons.scss";
export interface ProductProps {
products: {
id: number;
name: string;
price: number;
desc: string;
img: string;
units: number;
buttonText: string;
}[];
}
const ProductCard: React.FC<ProductProps> = (props) => {
/* const [isLoading, setIsLoading] = useState(false); */
const [buttonText, setButtonText] = useState(props.products);
const handleClick = (index: number) => {
const newText = JSON.parse(JSON.stringify(buttonText));
const testing = newText || [];
testing[index].buttonText = "Adding to cart...";
setButtonText(newText);
setTimeout(() => {
setButtonText(props.products);
console.log(JSON.parse(JSON.stringify(buttonText)));
}, 2000);
};
return (
<>
{props.products.length > 0 &&
props.products.map((it, index) => (
<article key={it.id} className="product-card">
<img src={it.img} alt={it.name} />
<h3>{it.name}</h3>
<p>{it.desc}</p>
<h4>{it.price}.00€</h4>
<div className="product-card__button--wrapper">
<button
className="product-card-button"
onClick={() => {
handleClick(index);
console.log(it.id);
}}
key={it.id}
>
{it.buttonText}
</button>
<button className="product-card-button">info</button>
</div>
</article>
))}
</>
);
};
export default ProductCard;
````Thanks!!!
Try to adjust the following line of code :
const [buttonText, setButtonText] = useState<string>(props.products?.buttonText);

How to pass function as many prop in react typescript (useState([]), useState(boolean))

API Structure
{
"default": [
{
"id": 1,
"name": "A"
},
{
"id": 2,
"name": "B"
},
{
"id": 3,
"name": "C"
},
]
}
i want to handling many props in child component,,, :(
many props example is useState([]), useState(boolean)
export interface iItemListProps {
id: number;
name: string;
}
export interface iDepth {
depth: boolean;
}
function App() {
const [itemList, setItemList] = useState([]);
const [depth, setDepth] = useState(false);
useEffect(() => {
fetch("API URL")
.then((res) => res.json())
.then((res) => {
setItemList(res);
});
}, []);
return (
<div className="wrap">
<ul className="tabList">
<li className="active">1</li>
<li>2</li>
<li>3</li>
</ul>
<div className="listContainer">
<Product itemList={itemList} {...depth} />
</div>
</div>
);
}
component itemList error is
Type 'never[]' is not assignable to type '[{ id: number; name: string; }]'.
Target requires 1 element(s) but source may have fewer.
App.tsx
type iItemListProps = {
itemList: [
{
id: number;
name: string;
}
];
depth: boolean;
};
const Product: React.FunctionComponent<iItemListProps> = ({
depth,
itemList,
}) => {
console.log(depth);
return (
<div className="weekly">
<p className="title">이번주 채소</p>
{itemList.map((item) => (
<span key={item.id} className="item">
{item.name}
</span>
))}
<Allergy />
</div>
);
};
Please tell me if there is a good way.....
The problem lies here: const [itemList, setItemList] = useState([]);
here you are initialising your state with an empty array and not with your itemList type which is an array of object..
Do this:
const [itemList, setItemList] = useState([{id: 0; name: '';}]);
OR
const [itemList, setItemList] = useState([{}]);
Also, with no value in your state and using it with map may cause problem like this.
In your App.tsx use && condition so that it will execute only when you have data in your itemList.
{itemList && itemList.map((item) => (
<span key={item.id} className="item">
{item.name}
</span>
))}
You need to specify what type useState handles, for example:
useState<IItemListProps>([]);
/**
IItemListProps = {
id: number;
name: string;
}[]
*/
You can do it because of useState signature
function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>];
Error happens because [] type by default inferred as never[]
You have to mention the type in useState. For example:
const [itemList, setItemList] = useState<iItemListProps[]>([]);
Or have to give initial state in the useState. For example:
const [itemList, setItemList] = useState([{}]);
Or
const [itemList, setItemList] = useState([{id: 0, name: 'name'}]);
Hope problem is solved :)

Resources