I have the following code, which the purpose it is to be reused inside another component, and give a different link according to the id of my object, and my API return.
The problem is that, how I'm using a map function it is reproducing the same button multiple times on the screen. I want to know how I get only one button. Can I somehow use indexOf inside that map?
Everything else is working just fine
const ButtonGroup = (linksReturn) => {
return linksReturn.map((urlAdress, index) => (
<div key={index}>
<button
target="_blank"
href={urlAdress["detailsUrl"]}
>
Acess Link
</button>
</div>
));
};
const PreviewActions = ({ linksReturn }) => {
return <div>{ButtonGroup(linksReturn)}</div>;
};
export default PreviewActions;
This is the part of the code where I reuse the button
import { useEffect } from 'react';
import { useParams } from 'react-router-dom';
import PreviewActions from './PreviewActions';
// ** Store & Actions
import { getInfo } from '../store/actions/index';
import { useDispatch, useSelector } from 'react-redux';
const LicitacaoPreview = () => {
const dispatch = useDispatch();
const store = useSelector((state) => state.allInfo);
const { id } = useParams();
useEffect(() => {
dispatch(getInfo(id));
}, [dispatch]);
return typeof store.lotesData === 'object' ? (
<div>
<PreviewActions
id={id}
linksReturn={store.infoData}
/>
</div>
)
};
export default LicitacaoPreview;
Array.prototype.map() does not work like that, it calls the function passed in as an argument on every single array elements and returns the resultant array.
A better approach would be using Array.prototype.slice() to return specific part of the original array and then use map() on it
const ButtonGroup = (linksReturn) => {
const newArr = linksReturn.slice(startindex, endindex); // replace with your index appropriately
return newArr.map((urlAdress, index) => (
<div key={index}>
<button
target="_blank"
href={urlAdress["detailsUrl"]}
>
Acess Link
</button>
</div>
));
};
Related
I want to add to the array a new object everytime I click at a card, but when I do so it changes the last key-pair to the new one and it doesnt add it. I chose this method of updating the state since I saw it is more popular than the one with the push.
import React, { useState, useEffect } from "react";
import { Link } from "react-router-dom";
import { AiOutlineHeart, AiFillHeart } from "react-icons/ai";
import styles from "./MovieCard.module.css";
const imagePrefixUrl = "http://image.tmdb.org/t/p/w500";
const MovieCard = (props) => {
const [items, setItems] = useState(
JSON.parse(localStorage.getItem("favorites")) || []
);
const [liked, setLiked] = useState(false);
const movie = props?.movie;
const addFavoriteHandler = (movie) => {
setItems((data) => [...data, movie]);
};
useEffect(() => {
localStorage.setItem("favorites", JSON.stringify(items));
}, [items]);
return (
<div className={styles.container}>
{liked ? (
<button className={styles.heartIcon} onClick={() => setLiked(false)}>
<AiFillHeart />
</button>
) : (
<button
className={styles.heartIcon}
onClick={() => addFavoriteHandler(movie)}
>
<AiOutlineHeart />
</button>
)}
<Link to={`/movie/${movie.id}`} title={movie?.title}>
<img src={`${imagePrefixUrl}${movie?.backdrop_path}`} />
<p>{movie?.title}</p>
</Link>
</div>
);
};
export default MovieCard;
I am assuming from the component name MovieCard, that your app would have multiple instances of this component under a parent component (assumed to be MovieCardList).
A solution to your issue would be to lift the state and addFavoriteHandler
const [items, setItems] = useState(
JSON.parse(localStorage.getItem("favorites")) || []
);
to the parent component MovieCardList and pass the handler addFavoriteHandler as a prop to each MovieCard.
This would ensure that you have a single point for updating your localStorage key favorites and it would not be overridden by new update.
The reason for the override issue you are experiencing is that each card has an instance of items and it does not fetch the latest value of favorites from the localStorage before updating it, meaning it would always override the favorites in localStorage as per the current code.
I have a basic increment app in react with the following code. Passing in the handleClick function into the first button works fine, however passing it to a child component IcrementButton that returns the exact same button gives the error:
React: Expected `onClick` listener to be a function, instead got a value of `object. Why is it working for the first button but not the child component button and how can it be fixed? thanks in advance.
import { useState } from "react";
import "./styles.css";
const IncrementButton = (handleClick) => {
return (
<button onClick={handleClick}>+</button>
)
}
export default function App() {
const [num, setNum] = useState(0)
const handleClick = (e) => {
e.preventDefault()
console.log('clicked')
setNum(prev => prev += 1)
}
return (
<div className="App">
<div>{num}</div>
<button onClick={handleClick}>+</button>
<IncrementButton handleClick={handleClick} />
</div>
);
}
Since the IncrementButton is a custom component all props passed to it is sent as an object and to access it you need to use props.property. There are 2 ways to get your code to work.
Use props.property
const IncrementButton = (props) => {
return (
<button onClick={props.handleClick}>+</button>
)
}
Destructure the props object
const IncrementButton = ({handleClick}) => {
return (
<button onClick={handleClick}>+</button>
)
}
You didn't destructure handle click. Try the code below
const IncrementButton = ({handleClick}) => {
return (
<button onClick={handleClick}>+</button>
)
}
That does not work because React passes every props on a Component as a single Object.
You have two ways to get handleClick function reference.
Destructure props (es6)
const IncrementButton = ({handleClick}) => {
return (
<button onClick={handleClick}>+</button>
)
}
Use props.propertyName
const IncrementButton = (props) => {
return (
<button onClick={props.handleClick}>+</button>
)
}
I want to add an array of <TokenFeed/> functional components to the <Feeds/> component. The problem is that when I do, the onClick() event that passes some text to my <App/> doesn't work as expected. The array version of <TokenFeed/> when clicked, will replace my <input/> text rather than appending to the end of it.
On the contrary, when I add a copy of <TokenFeed/> in the return of <Feeds/>,
and click the button, it works fine.
How can I fix this?
Demo
import React, { useState } from "react";
import { Feeds } from "./Feeds";
export default function App() {
const [inputValue, setInputValue] = useState("");
const [showFeeds, setShowFeeds] = useState();
function createFeeds(e) {
if (e._reactName === "onClick" && showFeeds === undefined) {
setShowFeeds(
<Feeds
value={(val) => {
setInputValue(inputValue + val);
createFeeds("");
}}
/>
);
} else {
setShowFeeds(undefined);
}
}
return (
<>
<input
type="text"
placeholder="Message"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
></input>
<button onClick={(e) => createFeeds(e)}>Create Feeds</button>
{showFeeds}
</>
);
}
import React from "react";
import { TokenFeed } from "./TokenFeed";
let tokenFeedArr = [];
export const Feeds = (props) => {
if (tokenFeedArr.length === 0) {
tokenFeedArr.push(
<TokenFeed
key={"1"}
onClick={() => props.value("Array")}
tokenName={"Array"}
tokenPrice={"Test"}
/>
);
}
return (
<section>
{/* This doesn't work */}
{tokenFeedArr}
{/* This does work */}
<TokenFeed
key={"2"}
onClick={() => props.value("Direct")}
tokenName={"Direct"}
tokenPrice={"Test"}
/>
</section>
);
};
import React from "react";
export const TokenFeed = (props) => {
return (
<section
onClick={() => props.onClick()}
style={{ backgroundColor: "yellow", width: "10%", textAlign: "center" }}
>
<h1>{props.tokenName}</h1>
<p>{props.tokenPrice}</p>
</section>
);
};
You need to declare let tokenFeedArr = []; inside the Feeds component.
Instead of:
let tokenFeedArr = [];
export const Feeds = (props) => {
if (tokenFeedArr.length === 0) {
tokenFeedArr.push(
<TokenFeed
key={"1"}
onClick={() => props.value("Array")}
tokenName={"Array"}
tokenPrice={"Test"}
/>
);
}
...
Try this:
export const Feeds = (props) => {
const tokenFeedArr = [];
if (tokenFeedArr.length === 0) {
tokenFeedArr.push(
<TokenFeed
key={"1"}
onClick={() => props.value("Array")}
tokenName={"Array"}
tokenPrice={"Test"}
/>
);
}
...
The reason the tokenFeedArr that is declared outside the Feeds component doesn't work has to do with JavaScript closures. Specifically the issue lies within the value() function that's inside instances of TokenFeed inside the tokenFeedArr.
The value() function that is passed to an instance of TokenFeed inside of the tokenFeedArr only has access to inputValue as it was when the component was mounted (which is an empty string). It's not connected to inputValue via React state, because it's outside of the scope of the exported component. This is true even though the TokenFeed components are pushed to tokenFeedArr inside the Feeds component. tokenFeedArr is still declared outside Feeds. The setInputValue function works, because of how the useState hook works, but the inputValue variable is just a variable and is subject to JavaScript closures/hoisting, which causes it to retain its original value.
I don't understand why the second line, which reads data from the props, is not displayed as instantly as the first, i would like them to be displayed instantly
I update the state when a button is clicked, which calls api, data is coming in, the state is updating, but the second line requires an additional press to display
How to display both lines at once after a call? What's my mistake?
I'm using react hooks, and i know that required to use useEffect for re-render component, i know, that how do work asynchronous call,but i'm a little confused, how can i solve my problem, maybe i need to use 'useDeep effect' so that watching my object properties, or i don't understand at all how to use 'useEffect' in my situation, or even my api call incorrectly?
I have tried many different solution methods, for instance using Promise.all, waiting for a response and only then update the state
index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./test";
ReactDOM.render(<App />, document.getElementById("root"));
app.js
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
const useDataApi = (initialState) => {
const [state, setState] = useState(initialState);
const stateCopy = [...state];
const setDate = (number, value) => {
setState(() => {
stateCopy[number].date = value;
return stateCopy;
});
};
const setInfo = async () => {
stateCopy.map((item, index) =>
getFetch(item.steamId).then((res) => setDate(index, res.Date))
);
};
const getFetch = async (id) => {
if (id === "") return;
const requestID = await fetch(`https://api.covid19api.com/summary`);
const responseJSON = await requestID.json();
console.log(responseJSON);
const result = await responseJSON;
return result;
};
return { state, setState, setInfo };
};
const Children = ({ data }) => {
return (
<>
<ul>
{data.map((item) => (
<li key={item.id}>
{item.date ? item.date : "Not data"}
<br></br>
</li>
))}
</ul>
</>
);
};
const InfoUsers = ({ number, steamid, change }) => {
return (
<>
<input
value={steamid}
numb={number}
onChange={(e) => change(number, e.target.value)}
/>
</>
);
};
function App() {
const usersProfiles = [
{ date: "", id: 1 },
{ date: "", id: 2 }
];
const profiles = useDataApi(usersProfiles);
return (
<div>
<InfoUsers number={0} change={profiles.setID} />
<InfoUsers number={1} change={profiles.setID} />
<button onClick={() => profiles.setInfo()}>Get</button>
<Children data={profiles.state} loading={profiles} />
</div>
);
}
export default App;
To get the data, just click GET
In this example, completely removed useEffect, maybe i don’t understand how to use it correctly.
P.s: Sorry for bad english
You don't need stateCopy, as you have it in the callback of the setState:
const setInfo = async () => {
// we want to update the component only once
const results = await Promise.all(
state.map(item => getFetch(item.steamId))
);
// 's' is the current state
setState(s =>
results.map((res, index) => ({ ...s[index], date: res.Date })
);
};
I have made a basic application to practice React, but am confused as to why, when I try to delete a single component from an state array, all items after it get deleted too. Here is my basic code:
App.js:
import React from 'react'
import Parent from './Parent';
import './App.css';
function App() {
return (
<div className="App">
<Parent />
</div>
);
}
export default App;
Parent.js:
import React, { useState } from 'react';
import ListItem from './ListItem';
import './App.css';
function Parent() {
const [itemList, setItemList] = useState([])
const [numbers, setNumbers] = useState([])
const addItem = () => {
const id = Math.ceil(Math.random()*10000)
const newItem = <ListItem
id={id}
name={'Item-' + id}
deleteItem={deleteItem}
/>
const list = [...itemList, newItem]
setItemList(list)
};
const deleteItem = (id) => {
let newItemList = itemList;
newItemList = newItemList.filter(item => {
return item.id !== id
})
setItemList(newItemList);
}
const addNumber = () => {
const newNumbers = [...numbers, numbers.length + 1]
setNumbers(newNumbers)
}
const deleteNum = (e) => {
let newNumbers = numbers
newNumbers = newNumbers.filter(n => n !== +e.target.innerHTML)
setNumbers(newNumbers);
}
return (
<div className="Parent">
List of items:
<div>
{itemList}
</div>
<button onClick={addItem}>
Add item
</button>
<div>
List of numbers:
<div>
{numbers.map(num => (
<div onClick={deleteNum}>{num}</div>
))}
</div>
</div>
<button onClick={addNumber}>
Add number
</button>
</div>
);
};
export default Parent;
ListItem.js:
import React from 'react';
import './App.css';
function ListItem(props) {
const { id, name, deleteItem } = props;
const handleDeleteItem = () => {
deleteItem(id);
}
return (
<div className="ListItem" onClick={handleDeleteItem}>
<div>{name}</div>
</div>
);
};
export default ListItem;
When I add an item by clicking the button, the Parent state updates correctly.
When I click on the item (to delete it), it deletes itself but also every item in the array that appears after it <-- UNWANTED BEHAVOUR. I only want to delete the specific item.
I have tested it with numbers too (not creating a separate component). These delete correctly - only the individual number I click on is deleted.
As far as I can tell, the individual item components are saving a reference as to what the Parent state value was when they are created. This seems like very strange behaviour to me...
How do I delete only an individual item from the itemList state array when they are made up of separate components?
Thanks
EDIT: As per the instruction from Bergi, I fixed the issue by converting the 'itemList' state value to an array of objects to render (and rerender) when the list is changed instead:
const addItem = () => {
const id = Math.ceil(Math.random()*10000);
const newItem = {
id: id,
name: 'Item-' + id,
}
const newList = [...itemList, newItem]
setItemList(newList)
}
...
React.useEffect(() => {
}, [itemList]);
...
<div className="Parent">
List of items:
<div>
{itemList.map(item => {
return (<ListItem
id={item.id}
name={item.name}
deleteItem={deleteItem}
/>);
})}
...
The problem is that your deleteItem function is a closure over the old itemList, back from the moment in which the item was created. Two solutions:
use the callback form of setItemList
don't store react elements in that list, but just plain objects (which you can use as props) and pass the (most recent) deleteItem function only when rendering the ListItems