How i can refresh this function on started value - reactjs

Hi! i have a problem with my state in React, I have two onMouse functions, the first one is to add an element and the second one is to delete, unfortunately the second one does not delete and the added element 'opacity' is rendered.
let menuItems = ['Tasks', 'Issues', 'Files', 'Raports']
const [item, setItem] = useState(menuItems)
const handleSpace = (id) => {
menuItems.splice(id, 0, 'opacity')
setItem([...item])
}
const restart = () => {
menuItems = ['Tasks', 'Issues', 'Files', 'Raports']
setItem([...item])
}
return (
<>
<div className="dashboard" style={slide.flag ? {left: '-105%'} : {left: '0%'}}>
<div className="dashboard__nav">
<ul className="dashboard__nav-list">
{item.map((item, id) => {
return <li className="dashboard__nav-item" key={id} onMouseOver={() => handleSpace(id)} onMouseLeave={restart}>{item}</li>
})}
</ul>
</div>
<div className="dashboard__array">
{tasks.map((task, id) => {
return (
<div className="dashboard__array-item" key={id}>
<div className="dashboard__array-item-header">
<p className="dashboard__array-item-header-title">{task}</p>
<button className="dashboard__array-item-header-cancel">
<FontAwesomeIcon icon={faCancel} />
</button>
</div>
<div className="dashboard__array-item-main">
<p className="dashboard__array-item-main-description">{descriptionTasks[id]}</p>
<p className="dashboard__array-item-main-button">Show More</p>
</div>
</div>
)
})}
</div>
</div>
</>
)
I already created setItem(menuItems), it removed the element 'opacity, but juz it didn't add it a second time

It seems that the two functions might be over complicating the handling of the item state.
Try handle setItem without changing another variable menuItems, so it can be used as a reset value at anytime.
Example:
const menuItems = ["Tasks", "Issues", "Files", "Raports"];
const [item, setItem] = useState(menuItems);
const handleSpace = (id) =>
setItem((prev) => {
const newItems = [...prev];
newItems.splice(id, 0, "opacity");
return newItems;
});
const restart = () => setItem(menuItems);
Hope this will help.

Related

How to set hover in a React loop and effect only one instead of all elements in the loop?

When I use setHover it reflects to all list data which returned from map loop. How can I use hover to reflect on itself element?
const [hover, setHover] = useState(true)
function MouseOver(event) {
setHover(true)
}
function MouseOut(event){
setHover(false)
}
{data.map((item, index) => (
//When I hover parent div I want to show the {item.arrow} div inside and not all {item.arrow} divs in the loop
<div key={index} onMouseEnter={MouseOver} onMouseLeave={MouseOut} className="flex gap-3">
<div>
{item.content}
</div>
<div hidden={hover}>
{item.arrow}
</div>
</div>
))}
If the state does not need to be controlled by the parent you can create a new component to use in the list.
Each component will then control its own hover state.
const List = ({data}) => {
return (
<div>
{
data.map((item, index) => (<Item key={index} item={item} />))
}
</div>
)
}
const Item = ({item}) => {
const [hover, setHover] = useState(true)
const mouseOver = (event) => {
setHover(true)
}
const mouseOut = (event) => {
setHover(false)
}
return (
<div onMouseEnter={mouseOver} onMouseLeave={mouseOut} className="flex gap-3">
<div>
{item.content}
</div>
<div hidden={hover}>
{item.arrow}
</div>
</div>
);
}
If the state does need to be controlled by the parent you can use a Record<number, boolean> to store the states.
const List = ({data}) => {
const [hover, setHover] = useState({})
const mouseOver = (event, index) => {
setHover(c => {
return {
...c,
[index]: true
};
})
}
const mouseOut = (event, index) => {
setHover(c => {
return {
...c,
[index]: false
};
})
}
return (
<div>
{
data.map((item, index) => (
<div
key={index}
onMouseEnter={(e) => {
mouseOver(e, index);
}}
onMouseLeave={(e) => {
mouseOut(e, index);
}}
className="flex gap-3"
>
<div>
{item.content}
</div>
<div hidden={hover[index]}>
{item.arrow}
</div>
</div>
))
}
</div>
)
}
If the state is not needed for anything other than hiding a div you could also just use CSS.
CSS will not require the component to rerender everytime you hover over it.
CSS
.hoverable-show {
display: none;
}
.hoverable-item:hover .hoverable-show {
display: block;
}
JS
const List = ({data}) => {
return (
<div>
{
data.map((item, index) => (
<div
className="flex gap-3 hoverable-item"
>
<div>
{item.content}
</div>
<div className="hoverable-show">
{item.arrow}
</div>
</div>
))
}
</div>
)
}
Preference should be CSS -> Individual State -> Parent (list) State.
This looks like a use case for the useReducer hook available right from the react library.

unable to select another option after selection with react select

Updated code: Im trying to first display carsList and only when selectedMake is selected, I would update the state with the result from filter and show another array. I tried storing carsList in updatedCarsList so it has all the cars on page load but Im missing something here.
CarOffers.jsx
const CarOffers = () => {
const [carsList, setCarsList] = useState([]);
const [updatedCarsList, setUpdatedCarsList] = useState([]);
const [selectedMake, setSelectedMake] = useState(undefined);
const getCars = () => {
axios.get(url)
.then((response) => {
return setCarsList(response.data)
})
}
const handleMakeChange = (select) => {
setSelectedMake(select.value)
}
const applyFilters = () => {
let updatedCarsList = carsList
if(selectedMake) {
updatedCarsList = carsList.filter(car => car.make === selectedMake)
setUpdatedCarsList(updatedCarsList);
} else {
setUpdatedCarsList(carsList)
}
}
useEffect(() => {
getCars()
applyFilters()
}, [ selectedMake ]);
return (
<div className="mka__wrapper-car-offers">
<div className="mka__container">
<div className="mka__content-car-offers">
<div className="mka__content-grid-offers">
<div className="item1">
< CarSlider/>
<div className="mka-responsive-item">
< DisplayCars/>
< SortingCars/>
< CarAlignment/>
</div>
</div>
<div className="item2">
<div className="mka__side-bar-divider">
< Search
carsList={carsList}/>
</div>
<div>
< FilterSideBar
carsList={carsList}
handleMakeChange={handleMakeChange} />
</div>
</div>
<div className="item3">
<Cars updatedCarsList={updatedCarsList}/>
</div>
</div>
</div>
</div>
</div>
)
}
export default CarOffers;
Cars.jsx
const Cars = ({ updatedCarsList }) => {
return (
<div className='mka__cars-grid'>
{updatedCarsList.map(car =>
<CarsItem key={car.id} car={car}/>)}
</div>
)
}
export default Cars
CarItem.jsx
const CarsItem = ({car: {year,month,transmission,mileage,price,title,link}}) => {
return (
<Fragment>
<div className="cars-item_wrapper">
<div className="cars-item_image">
<img src={link} alt="car" />
</div>
<div>
<a
className="cars-item_car-title"
href="/"
>
{title}
</a>
</div>
<div className=" cars-item_separator"></div>
<p className="cars-item_car-text">{price}</p>
</div>
</Fragment>
)
}
export default CarsItem
Move your applyFilters above getCars
Does Select need to be in <>
distinctBy... urgh.. use Set const unique = [...new Set(data.map(item => item.value))]
applyFilters... axios is async, but your setting a value so state doesn't update so no re-render? Maybe.
selectedMake - don't use null as a default, use undefined.
Hope that helps, feels like a state management issue.
... think its this ....
You are using carsList as your list of cars, however you are setting the value of carsList with setCarsList(updatedCarsList)... updatedCarsList is a filtered list of cars... only car => car.make === selectedMake so once you've selected a make your carList is only cars with the selected make.
Solution is to
Either separate the list from the filtered list
or preferably keep list, but pass the filtered state to the component that needs it... but not update state of the original list by calling setCarsList(updatedCarsList);
if (selectedMake){
updatedCarsList = updatedCarsList.filter(
car => car.make === selectedMake
)
};
setCarsList(updatedCarsList);

Use State not updating as expected

Fairly new to react and trying to build a clone of The Movie Database site. I want this toggle switch to change my api call from movies to tv. It starts working after a couple clicks, but then it throws everything off and it's not displaying the correct items anyway. Not really sure what's going on here...or even why it starts working after two clicks. Anyone know whats up with this?
import React, { useState, useEffect } from "react";
import axios from "axios";
import API_KEY from "../../config";
const Popular = ({ imageUri }) => {
// GET POPULAR MOVIES
const [popularMovies, setPopularMovies] = useState("");
const [genre, setGenre] = useState("movie");
console.log(genre);
const getPopular = async () => {
const response = await axios.get(
`https://api.themoviedb.org/3/discover/${genre}?sort_by=popularity.desc&api_key=${API_KEY}`
);
setPopularMovies(response.data.results);
};
useEffect(() => {
getPopular();
}, []);
const listOptions = document.querySelectorAll(".switch--option");
const background = document.querySelector(".background");
const changeOption = (el) => {
let getGenre = el.target.dataset.genre;
setGenre(getGenre);
getPopular();
listOptions.forEach((option) => {
option.classList.remove("selected");
});
el = el.target.parentElement.parentElement;
let getStartingLeft = Math.floor(
listOptions[0].getBoundingClientRect().left
);
let getLeft = Math.floor(el.getBoundingClientRect().left);
let getWidth = Math.floor(el.getBoundingClientRect().width);
let leftPos = getLeft - getStartingLeft;
background.setAttribute(
"style",
`left: ${leftPos}px; width: ${getWidth}px`
);
el.classList.add("selected");
};
return (
<section className="container movie-list">
<div className="flex">
<div className="movie-list__header">
<h3>What's Popular</h3>
</div>
<div className="switch flex">
<div className="switch--option selected">
<h3>
<a
data-genre="movie"
onClick={(e) => changeOption(e)}
className="switch--anchor"
>
In Theaters
</a>
</h3>
<div className="background"></div>
</div>
<div className="switch--option">
<h3>
<a
data-genre="tv"
onClick={(e) => changeOption(e)}
className="switch--anchor"
>
On TV
</a>
</h3>
</div>
</div>
</div>
<div className="scroller">
<div className="flex flex--justify-center">
<div className="flex flex--nowrap container u-overScroll">
{popularMovies &&
popularMovies.map((movie, idX) => (
<div key={idX} className="card">
<div className="image">
<img src={imageUri + "w500" + movie.poster_path} />
</div>
<p>{movie.title}</p>
</div>
))}
</div>
</div>
</div>
</section>
);
};
export default Popular;
You're using the array index as your key prop when you're mapping your array.
You should use an id that is specific to the data that you're rendering.
React uses the key prop to know which items have changed since the last render.
In your case you should use the movie id in your key prop instead of the array index.
popularMovies.map((movie) => (
<div key={movie.id} className="card">
<div className="image">
<img src={imageUri + 'w500' + movie.poster_path} />
</div>
<p>{movie.title}</p>
</div>
));
Also
You're calling the api directly after setGenre. However state changes aren't immediate. So when you're making your api call you're still sending the last movie genre.
Two ways of fixing this:
You could call your function with the genre directly, and change your function so it handles this case:
getPopular('movie');
Or you could not call the function at all and add genre as a dependency of your useEffect. That way the useEffect will run each time the genre change.
useEffect(() => {
getPopular();
}, [genre]);
PS: You should consider splitting your code into more component and not interacting with the DOM directly.
To give you an idea of what it could look like, I refactored a bit, but more improvements could be made:
const Popular = ({ imageUri }) => {
const [popularMovies, setPopularMovies] = useState('');
const [genre, setGenre] = useState('movie');
const getPopular = async (movieGenre) => {
const response = await axios.get(
`https://api.themoviedb.org/3/discover/${movieGenre}?sort_by=popularity.desc&api_key=${API_KEY}`
);
setPopularMovies(response.data.results);
};
useEffect(() => {
getPopular();
}, [genre]);
const changeHandler = (el) => {
let getGenre = el.target.dataset.genre;
setGenre(getGenre);
};
const isMovieSelected = genre === 'movie';
const isTvSelected = genre === 'tv';
return (
<section className="container movie-list">
<div className="flex">
<MovieHeader>What's Popular</MovieHeader>
<div className="switch flex">
<Toggle onChange={changeHandler} selected={isMovieSelected}>
In Theaters
</Toggle>
<Toggle onChange={changeHandler} selected={isTvSelected}>
On TV
</Toggle>
</div>
</div>
<div className="scroller">
<div className="flex flex--justify-center">
<div className="flex flex--nowrap container u-overScroll">
{popularMovies.map((movie) => {
const { title, id, poster_path } = movie;
return (
<MovieItem
title={title}
imageUri={imageUri}
key={id}
poster_path={poster_path}
/>
);
})}
</div>
</div>
</div>
</section>
);
};
export default Popular;
const Toggle = (props) => {
const { children, onChange, selected } = props;
const className = selected ? 'switch--option selected' : 'switch--option';
return (
<div className={className}>
<h3>
<a
data-genre="movie"
onClick={onChange}
className="switch--anchor"
>
{children}
</a>
</h3>
<div className="background"></div>
</div>
);
};
const MovieHeader = (props) => {
const { children } = props;
return (
<div className="movie-list__header">
<h3>{children}</h3>
</div>
);
};
const MovieItem = (props) => {
const { title, imageUri, poster_path } = props;
return (
<div key={idX} className="card">
<div className="image">
<img src={imageUri + 'w500' + poster_path} />
</div>
<p>{title}</p>
</div>
);
};

React array state is not Iterable

I am trying to figure out how to pass an item thru the state on the item: [] inside the list state. Whenever I tried this code, an error shows up as lists is not iterable whenever I insert or add item to the array
Is there a way to insert data to the array property of the state? And adding more string arrays in that property?
const [lists, setLists] = useState({
item: [],
});
const addList = () => {
const listItem = document.getElementById("listItem");
if (listItem.value !== "") {
setLists([
...lists,
{
item: listItem.value,
},
]); // >>> [INSERT TO THE ARRAY PROPERTY]
listItem.value = "";
}
};
return (
<div>
<form onSubmit={handleSubmit}>
<input
type="text"
id="listItem"
name="item"
onKeyPress={(e) => (e.key === "Enter" ? addList() : null)}
/>
<button
type="button"
onClick={() => {
addList();
}}
>
Add
</button>
<ul>
LIST
{lists.item.map((val, index) => {
return (
<li key={index}>
<p>{val}</p>
<button type="button" onClick={() => removeList(index)}>
Remove
</button>
</li>
);
})}
</ul>
<button type="submit">submit</button>
</form>
</div>
);
You seem to be having some confusion regarding your data types. lists is an array of objects of the shape {item: ...}.
The useState call should be useState([]).
You'll need lists.map(({item}, index) => (or lists.map(val and val.item) to get at the ....
You can use e.g. console.log(lists), or a debugger, to see what's really happening.)
You shouldn't use document.getElementById() with React, ever. Instead, make the input controlled (or have a ref to it and read the value if you want uncontrolled, but you likely don't).
The setLists call should be the functional form: setLists(lists => [...lists, {item: listItem.value}]).
All in all, something like
function Component() {
const [newItemText, setNewItemText] = React.useState("");
const [todoList, setTodoList] = React.useState([]);
const addList = (event) => {
event.preventDefault();
if (newItemText !== "") {
setTodoList(todoList => [
...todoList,
{
item: newItemText,
},
]);
setNewItemText("");
}
};
return (
<div>
<form onSubmit={addList}>
<input
type="text"
name="item"
value={newItemText}
onChange={e => setNewItemText(e.target.value)}
/>
<button
type="submit"
>
Add
</button>
</form>
<ul>
LIST
{todoList.map(({item}, index) => {
return (
<li key={index}>
<p>{item}</p>
<button type="button">
Remove
</button>
</li>
);
})}
</ul>
</div>
);
}
ReactDOM.render(<Component />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.0/umd/react-dom.production.min.js"></script>
<div id="root">
const [lists, setLists] = useState({
item: [],
});
In the code above you set initial value as an Object not an Array. Try to change code like below.
const [lists, setLists] = useState([]);

React - Warning: Each child in a list should have a unique "key" prop

In this simple React App, I don't understand why I get the following warning message:
Warning: Each child in a list should have a unique "key" prop.
To me it seems that I put the key at the right place, in form of key={item.login.uuid}
How can I get rid of the warning message?
Where would be the right place to put the key?
App.js
import UserList from './List'
const App = props => {
const [id, newID] = useState(null)
return (
<>
<UserList id={id} setID={newID} />
</>
)
}
export default App
List.js
const UserList = ({ id, setID }) => {
const [resources, setResources] = useState([])
const fetchResource = async () => {
const response = await axios.get(
'https://api.randomuser.me'
)
setResources(response.data.results)
}
useEffect(() => {
fetchResource()
}, [])
const renderItem = (item, newID) => {
return (
<>
{newID ? (
// User view
<div key={item.login.uuid}>
<div>
<h2>
{item.name.first} {item.name.last}
</h2>
<p>
{item.phone}
<br />
{item.email}
</p>
<button onClick={() => setID(null)}>
Back to the list
</button>
</div>
</div>
) : (
// List view
<li key={item.login.uuid}>
<div>
<h2>
{item.name.first} {item.name.last}
</h2>
<button onClick={() => setID(item.login.uuid)}>
Details
</button>
</div>
</li>
)}
</>
)
}
const user = resources.find(user => user.login.uuid === id)
if (user) {
// User view
return <div>{renderItem(user, true)}</div>
} else {
// List view
return (
<ul>
{resources.map(user => renderItem(user, false))}
</ul>
)
}
}
export default UserList
The key needs to be on the root-level element within the loop. In your case, that's the fragment (<>).
To be able to do that, you'll need to write it out fully:
const renderItem = (item, newID) => {
return (
<Fragment key={item.login.uuid}>
{newID ? (
...
)}
</Fragment>
);
}
(You can add Fragment to your other imports from react).
Note that the fragment isn't actually needed in your example, you could drop it and keep the keys where they are since then the <div> and <li> would be the root element:
const renderItem = (item, newId) => {
return newID ? (
<div key={item.login.uuid}>
...
</div>
) : (
<li key={item.login.uuid}>
...
</li>
)
}
What if you create 2 separate components, one for the user view and one for the list item. That way you only need to pass the user prop. Also, use JSX and pass wht key from there.
const UserList = ({ id, setID }) => {
const [resources, setResources] = useState([])
const fetchResource = async () => {
const response = await axios.get(
'https://api.randomuser.me'
)
setResources(response.data.results)
}
useEffect(() => {
fetchResource()
}, [])
const User = ({user}) => (
<div key={user.login.uuid}>
<div>
<h2>
{user.name.first} {user.name.last}
</h2>
<p>
{user.phone}
<br />
{user.email}
</p>
<button onClick={() => setID(null)}>
Back to the list
</button>
</div>
</div>
)
const ListItem = ({user}) => (
<li key={user.login.uuid}>
<div>
<h2>
{user.name.first} {user.name.last}
</h2>
<button onClick={() => setID(user.login.uuid)}>
Details
</button>
</div>
</li>
)
const user = resources.find(user => user.login.uuid === id)
if (user) {
// User view
return <User user={user}</div>
} else {
// List view
return (
<ul>
{resources.map((user, index) => <ListItem key={index} user={user} />)}
</ul>
)
}
}
export default UserList

Resources