In two places it is necessary to display the component Items.
But in one place you need an additional component Filters, in another - not need. The Filters component is inside Items.
When I do this , it doesn't work:
const Items = ({ items, users, resourceUrl }) => {
const [goods, setGoods] = useState(items);
const [customers, SetCustomers] = useState(users);
const handleSubmit = e => {
e.preventDefault();
// ...
};
const changeUser = e => {
// ...
}
function DisplayFilter(props) {
const isDisplay = props.isDisplay;
const isUsers = props.users;
if (isDisplay == undefined) {
return (
<ItemsFilters changeUser={changeUser} users={isUsers} />
)
}
return null;
}
return (
<div>
<DisplayFilter isDisplay={resourceUrl} users={users}/>
{goods.map((element) => (
<Comment
date={element.date}
name={element.name}
doctor={element.user}
text={element.text}
/>
))}
<span className="btn-show_more">
<a className="button button_large" onClick={handleSubmit} rel="next">Show more</a>
</span>
</div>
)
};
The data transmitted is not the same.
If you remove the output of the condition and insert in return - <ItemsFilters changeUser={changeUser} users={isUsers} />, that all works. But then the filter is displayed in other places where it should not.
You can do like this
return (
<div>
{props.isDisplay
? (
<ItemsFilters changeUser={changeUser} users={props.users} />
)
: null}
{goods.map((element) => (
<Comment
date={element.date}
name={element.name}
doctor={element.user}
text={element.text}
/>
))}
<span className="btn-show_more">
<a className="button button_large" onClick={handleSubmit} rel="next">Show more</a>
</span>
</div>
)
You could write it like this:
const Items = ({ items, users, resourceUrl }) => {
const [goods, setGoods] = useState(items);
const [customers, SetCustomers] = useState(users);
const handleSubmit = e => {
e.preventDefault();
// ...
};
const changeUser = e => {
// ...
}
return (
<div>
{resourceUrl && <ItemsFilters changeUser={changeUser} users={users} />}
{goods.map((element) => (
<Comment
date={element.date}
name={element.name}
doctor={element.user}
text={element.text}
/>
))}
<span className="btn-show_more">
<a className="button button_large" onClick={handleSubmit} rel="next">Show more</a>
</span>
</div>
)
};
This will only render the Filter element, if your condition (resourceUrl) is present.
Personally, I like having my conditional statements defined explicitly and hold the component that I want to render conditionally in a variable.
The advantage with this is that it makes the code very clear and easy to understand and most importantly, it allows me to perform more complex conditions without getting my code all messy and hard to read as is usually the case with ternary operators.
const Items = ({ items, users, resourceUrl }) => {
const [goods, setGoods] = useState(items);
const [customers, SetCustomers] = useState(users);
const handleSubmit = e => {
e.preventDefault();
// ...
};
const changeUser = e => {
// ...
}
let itemFilters = null;
if(resourceUrl) {
itemFilters = <ItemsFilters changeUser={changeUser} users={users} />
}
return (
<div>
{itemFilters}
{goods.map((element) => (
<Comment
date={element.date}
name={element.name}
doctor={element.user}
text={element.text}
/>
))}
<span className="btn-show_more">
<a className="button button_large" onClick={handleSubmit} rel="next">Show more</a>
</span>
</div>
)
};
Related
I have a contentEditable component:
EditableComponent.js
const EditableComponent = (props) => {
return <p contentEditable>{props.children}</p>;
};
In the ParentComponent I can add EditableComponents to an array (someArr) with useState, and then I pass someArr and setSomeArray via props to another component (AllEditable) to render it:
ParentComponent.js
import EditableComponent from "./components";
import AllEditable from "./components";
const ParentComponent = () => {
const [someArr, setSomeArr] = useState([]);
const handleNewEditable = () => {
setContentArr((prevContentArr) => {
return [...prevContentArr, <EditableComponent />];
});
};
return (
<div className="wrapper">
<AllEditable someArr={someArr} setSomeArr={setSomeArr} />
<div>
<button onClick={handleNewEditable}>Add</button>
</div>
</div>
);
};
Each rendered component (EditableComponent) have a span with the content 'X' that should delete the target onClick:
AllEditable.js
const AllEditable= (props) => {
const deleteContentHandler = (index) => {
props.setSomeArr((prevState) => {
return prevState.filter((_, idx) => idx !== index);
});
};
return (
<div>
{props.someArr.map((content, idx) => {
return (
<div key={`content-${idx}`}>
<span onClick={() => {deleteContentHandler(idx);}}>
X
</span>
<div>{content}</div>
</div>
);
})}
</div>
);
};
The problem:
It doesn't matter which component I'm trying to delete, it removes the last component (even in the Components section in the developer tools) and I'm pretty sure that the logic behind deleting (filter) works well.
I tried deleting the contentEditable attribute, and added some unique random text in each component and then it worked as expected!.
Things I tried
Creating a new array without the removed target
Nesting the components in someArr in objects with key: index, example: {idx: 0, content: <EditableComponent />}
Added a function - onDoubleClick for each EditableComponent to toggle the attribute contentEditable, true or false.
Replaced the element in EditableComponent to <textarea></textarea> instead of <p contentEditable></p>
Your problem is all your EditableComponent components have the same key (because you haven't declared key on them). React cannot identify which EditableComponent you want to delete. You can check this document.
I'd suggest you add a key attribute like below
<EditableComponent key={prevContentArr.length - 1}/>
For safer index reservation, you should use map instead filter
const AllEditable= (props) => {
const deleteContentHandler = (index) => {
props.setSomeArr((prevState) => {
return prevState.map((x, idx) => idx !== index ? x : null);
});
};
return (
<div>
{props.someArr.map((content, idx) => {
return content ? (
<div key={`content-${idx}`}>
<span onClick={() => {deleteContentHandler(idx);}}>
X
</span>
<div>{content}</div>
</div>
) : null;
})}
</div>
);
};
const { useState } = React
const EditableComponent = (props) => {
return <p contentEditable>{props.children}</p>;
};
const AllEditable= (props) => {
const deleteContentHandler = (index) => {
props.setSomeArr((prevState) => {
return prevState.map((x, idx) => idx !== index ? x : null);
});
};
return (
<div>
{props.someArr.map((content, idx) => {
return content ? (
<div key={`content-${idx}`}>
<span onClick={() => {deleteContentHandler(idx);}}>
X
</span>
<div>{content}</div>
</div>
) : null;
})}
</div>
);
};
const ParentComponent = () => {
const [someArr, setSomeArr] = useState([]);
const handleNewEditable = () => {
setSomeArr((prevContentArr) => {
return [...prevContentArr, <EditableComponent key={prevContentArr.length - 1}/>];
});
};
return (
<div className="wrapper">
<AllEditable someArr={someArr} setSomeArr={setSomeArr} />
<div>
<button onClick={handleNewEditable}>Add</button>
</div>
</div>
);
};
ReactDOM.render(
<ParentComponent/>,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Side note that keys with index values are not the best because your array keeps changing that would make key changes as well. You can use some unique key generator to handle that situation instead.
You shluld change the way you set keys, setting the element key to: "content-index" is confusing for react, because once you remove an item all the indexes will change, and therefore your keys.
So you need to find a way to have static keys for your elements. That way everytime they render, the key will be the same.
The logic is working correctly and in fact it is deleting the correct elements, however since you are using the index to identify elements, you will never see this since it will always appear that only the last one is removed (when the array updates, the indices will update too).
So if you had 0,1,2 and removed 1 now you have an array with indices 0,1
To test this you can place a reference to the index when rendering the content editable div, based on testing you can see that the correct element is in fact being removed:
import "./styles.css";
import React, { useState } from "react";
const EditableComponent = (props) => {
return (
<p contentEditable>
{props.children}
{props.idtfy}
</p>
);
};
const AllEditable = (props) => {
const deleteContentHandler = (index) => {
props.setSomeArr((prevState) => {
return prevState.filter((_, idx) => idx !== index);
});
};
return (
<div>
{props.someArr.map((content, idx) => {
return (
<div key={`content-${idx}`}>
<span>{idx}</span>
<span
onClick={() => {
deleteContentHandler(idx);
}}
>
X
</span>
<div>{content}</div>
</div>
);
})}
</div>
);
};
const ParentComponent = () => {
const [someArr, setSomeArr] = useState([]);
const handleNewEditable = () => {
setSomeArr((prevContentArr) => {
return [
...prevContentArr,
<EditableComponent idtfy={prevContentArr.length + 1} />
];
});
};
return (
<div className="wrapper">
<AllEditable someArr={someArr} setSomeArr={setSomeArr} />
<div>
<button onClick={handleNewEditable}>Add</button>
</div>
</div>
);
};
That said, your key should probably point to some static value, but that will only impact rendering order, not which item is being clicked/closed.
This is my component of imageslider with button next and previous
I need som help how i can get individual img_src values and add them into another array and them use them in my image slider.
I welcome every solution corresponding to my aproach
const ImageSlider = () => {
const dispatch = useDispatch();
const ImageList = useSelector((state) => state.ImageList);
const { loading, error, Images } = ImageList;
useEffect(() => {
dispatch(ListImages());
}, [dispatch]);
var items = [Images.photos];
console.log(Images);
const classes = useStyles();
function Item(props) {
return (
<Paper>
{props.item.map(data => (
<img src={data.img_src} />
))}
{({ onClick, className, style, next, prev }) => {
return (
<Button onClick={onClick} className={classes.button} style={style}>
{next && "Next"}
{prev && "Previous"}
</Button>
);
}}
</Paper>
);
}
return (
<>
{loading ? (
<Loader />
) : error ? (
<h1>{error}</h1>
) : (
<Carousel>
{items.map((item, i) => (
<Item key={i} item={item} />
))}
</Carousel>
)}
</>
);
};
export default ImageSlider;
```
First of all you should move the Item component out of the ImageSlider. It is being redefined every render. You can use localState to keep track of the index.
const useImageIndexer = (maxIndex) => {
const [index, setIndex] = useState(0);
const nextImage = () => {
setIndex((current) => Math.min(maxIndex, current + 1));
};
const prevImage = () => {
setIndex((current) => Math.max(0, current - 1));
};
return [index, nextImage, prevImage];
}
Then to use inside the slider
const ImageSlider = () => {
const dispatch = useDispatch();
const ImageList = useSelector((state) => state.ImageList);
const photos = ImageList.Images.photos;
const [index, nextImage, prevImage] = useImageIndexer(photos.length);
const currentPhoto = photos[index];
// Further down in the code
if(loading) {
return (<Loader />);
}
if (error) {
return (<div>Oh no!</div>);
}
return (<div>
<img src={img.src} />
<button onClick={prevImage}>Previous</button>
<button onClick={nextImage}>Next</button>
</div>);
It seemed like you were wrapping photos which sounds like an array inside another array, that doesn't look right.
var items = [Images.photos];
When I click on a specific button I want to capture the {country} prop associated with it.
I tired the following
import React, { useState, useEffect } from 'react'
import axios from 'axios'
// ====================================================================[SEARCH-BAR]=======================================================
// search component
const SearchBar = (props) => {
// console.log(props);
const { searchString, searchOnChangeEventHandler } = props
return (
<>
<form>
<label>Search </label>
<input type='text' placeholder='type to search...' value={searchString} onChange={searchOnChangeEventHandler} />
</form>
</>
)
}
// ================================================================[COUNTRY_CARD]==========================================================
// countryCard component
const CountryCard = (props) => {
console.log(props);
return (
<div>
<p>countryName</p>
<p>capital</p>
<p>population</p>
<p>languages</p>
<ul>
<li>item</li>
<li>item</li>
</ul>
<p>image flag</p>
</div>
)
}
// ===================================================================[DISPLAY]===========================================================
// display component
const Display = (props) => {
const [showCountryCard, setShowCountryCard] = useState(false)
const [thisCountry, setThisCountry] = useState({})
// console.log(props);
const { countries, searchString } = props
// console.log(countries);
// eslint-disable-next-line eqeqeq
// searchString empty
if (searchString == false) {
return (
<>
<div>
<span>Type in SearchBar for a country...</span>
</div>
</>
)
}
// to count number of matches
const filteredResultsCount = countries.filter(country => country.name.toLowerCase().includes(searchString.toLowerCase())).length
// function to filterCountries
const filteredResults = (searchString, countries) => countries.filter(country => {
return country.name.toLowerCase().includes(searchString.toLowerCase())
})
// RENDER CONDITIONS
// searchString return <= 10 matches && >1 match
// event handler for show-btn
const showCardEventHandler = (event) => {
console.log(event.target.parentElement);
setShowCountryCard(!showCountryCard)
}
if (filteredResultsCount <= 10 && filteredResultsCount > 1) {
return (
<>
<ul>
{
filteredResults(searchString, countries).map(country =>
<li
key={country.numericCode}
country={country}
>
<span>{country.name}</span>
<button
value={showCountryCard}
onClick={showCardEventHandler}
>show</button>
</li>
)
}
</ul>
{
showCountryCard ? <p>show country card</p> : null
}
</>
)
}
// searchString returns >10 matches
if (filteredResultsCount > 10) {
return (
<span>{filteredResultsCount} matches!, please refine your search...</span>
)
}
// searchString returns ===1 match
if (filteredResultsCount === 1) {
return (
<>
{
filteredResults(searchString, countries).map(country => <CountryCard key={country.numericCode} country={country} />)
}
</>
)
}
// invalid searchString
if (filteredResultsCount === 0) {
return (
<span><strong>{filteredResultsCount} matches!</strong> please refine your search...</span>
)
}
}
// ===================================================================[APP]==============================================================
// app component
const App = () => {
// to store countries
const [countries, setCountries] = useState([])
// to fetch data from
const url = 'https://restcountries.eu/rest/v2/all'
useEffect(() => {
// console.log('effect');
axios
.get(url)
.then(response => {
// console.log('promise fulfilled');
const countries = response.data
// array of objects
setCountries(countries)
})
}, [])
// console.log('countries', countries.length);
// console.log(countries);
// to store search string
const [searchString, setSearchString] = useState('')
// event handler search input
const searchOnChangeEventHandler = (event) => setSearchString(event.target.value)
return (
<>
<h1>Countries Data</h1>
<SearchBar searchString={searchString} searchOnChangeEventHandler={searchOnChangeEventHandler} />
<br />
<Display countries={countries} searchString={searchString} />
</>
)
}
export default App
Please take a look at <Display/> component and in particular I'm trying to work on this part
const showCardEventHandler = (event) => {
console.log(event.target.parentElement);
setShowCountryCard(!showCountryCard)
}
if (filteredResultsCount <= 10 && filteredResultsCount > 1) {
return (
<>
<ul>
{
filteredResults(searchString, countries).map(country =>
<li
key={country.numericCode}
country={country}
>
<span>{country.name}</span>
<button
value={showCountryCard}
onClick={showCardEventHandler}
>show</button>
</li>
)
}
</ul>
{
showCountryCard ? <p>show country card</p> : null
}
</>
)
}
I want to be able to render a list of countries if they are more than 10 and allow a user to click on a specific country, which then will be used to render the <CountryCard/> component.
If there is only 1 matching value from search then I will directly display the country card component. The second functionality works.
After the following refactor the first functionality works, but Ima little confused as to why so I'm adding on to the post. This is the component being rendered and now I'm passing country prop onClick, like so
if (filteredResultsCount <= 10 && filteredResultsCount > 1) {
return (
<>
<ul>
{filteredResults(searchString, countries).map((country) => (
<li key={country.numericCode} country={country}>
<span>{country.name}</span>
<button
value={showCountryCard}
onClick={() => toggleCardEventHandler(country)}>
{showCountryCard ? 'hide' : 'show'}
</button>
</li>
))}
</ul>
{showCountryCard ? <CountryCard country={country} /> : null}
</>
);
}
The event handler is as follows
const toggleCardEventHandler = (country) => {
// console.log(country);
setShowCountryCard(!showCountryCard);
setCountry(country)
};
This works properly.
My question is, when I change the eventHandler onClick={toggleCardEventHandler(country)} it breaks, but shouldnt it be accessible through closure?
Also, if I change the code to this
onClick={() => {
toggleCardEventHandler()
setCountry(country)
}}
The code works the way I want but which is a better way to pass the value to the toggleCardEventHandler() and set the country there or to do it like this?
As I understand it you want to pass the country.name to your showCardEventHandler.
Update showCardEventHandler so it takes the event and the country name:
const showCardEventHandler = (event, countryName) => {
console.log(countryName);
setShowCountryCard(!showCountryCard)
}
Now pass the countryname to the function:
<li
key={country.numericCode}
country={country}
>
<span>{country.name}</span>
<button
value={showCountryCard}
onClick={e => showCardEventHandler(e, country.name)}
>show</button>
</li>
Since you are not using the event in showCardEventHandler you can remove it from the signature
const showCardEventHandler = (countryName) => {}
and call it with onClick={() => showCardEventHandler(country.name)}
I am having a hard time rendering components conditionally in React. I have successfully rendered 2 components (A and B) conditionally but couldn't find any successful way to add a third component (C) in our case
this is the code for 2 componnets:
function App() {
const [click, setClick] = useState(true);
const ShowA = () => setClick(true);
const ShowB = () => setClick(false);
return (
<>
<br />
<button onClick={ShowA}>A </button>
<button onClick={ShowB}>B </button>
<div className="App">
{click && <div> A </div>}
{!click && <div>B</div>}
</div>
</>
);
}
Is there any possible way I can add a third C component so I can toggle between them? I have been trying for 2 days but no success.
This is the link of Codesandbox if anyone's interested
https://codesandbox.io/s/musing-tesla-9gkpw?file=/src/index.js:100-481
You can put as many states as you want:
function App() {
const [displayA, setDisplayA] = useState(true);
const [displayB, setDisplayB] = useState(true);
const [displayC, setDisplayC] = useState(true);
const showA = () => {
setDisplayA(true);
setDisplayB(false);
setDisplayC(false);
}
const showB = () => {
setDisplayA(false);
setDisplayB(true);
setDisplayC(false);
};
const showC = () => {
setDisplayA(false);
setDisplayB(false);
setDisplayC(true);
};
return (
<>
<br />
<button onClick={showA}>A</button>
<button onClick={showB}>B</button>
<button onClick={showC}>C</button>
<div className="App">
{displayA && <div>A</div>}
{displayB && <div>B</div>}
{displayC && <div>C</div>}
</div>
</>
);
}
And you can even put other things in your state, like JSX elements:
function App() {
const [elementToDisplay, setElementToDisplay] = useState("");
const showA = () => {
setElementToDisplay(<div>A</div>)
}
const showB = () => {
setElementToDisplay(<div>B</div>)
}
const showC = () => {
setElementToDisplay(<div>C</div>)
}
return (
<>
<br />
<button onClick={showA}>A</button>
<button onClick={showB}>B</button>
<button onClick={showC}>C</button>
<div className="App">
{elementToDisplay}
</div>
</>
);
}
You can save a state for the current button, and then show the different button conditionally using object lookup:
Check https://codesandbox.io/s/kind-haslett-b0fv0
function App() {
const [currentButton, setCurrentButton] = useState('A');
return (
<>
<br />
<button onClick={() => setCurrentButton('A')}>A</button>
<button onClick={() => setCurrentButton('B')}>B</button>
<button onClick={() => setCurrentButton('C')}>C</button>
<div className="App">
{
({
A: <div>A</div>,
B: <div>B</div>,
C: <div>C</div>
})[currentButton]
}
</div>
</>
);
}
I've added a 'like' function to increase the like count on a photo in any given iteration.
The issue is all of the photos in the gallery get liked at the same time.
I want to like photos individually.
const Gallery = ({ initialData, initialDataSetTwo, initialDataSetThree }) => {
const [data, setData] = useState(initialData);
const [dataTwo, setDataTwo] = useState(initialDataSetTwo);
const [dataThree, setDataThree] = useState(initialDataSetThree);
const [likes, setLikes] = useState(0);
const addLike = () => {
setLikes(likes + 1);
};
return (
<Wrapper>
<Styles className="row">
<Div className="col-xs-4">
{data.map(item => (
<MyImage
key={item.id}
src={item.fields.image.file.url}
header={item.fields.name}
likes={likes}
addLike={addLike}
/>
))}
</Div>
Here, within the icon, I have an onClick function that update the likes.
Again, I'm able to increment likes, but all the photos get liked at once.
How should I do this? Thanks!
const MyImage = ({ src, header, likes, addLike }) => {
const [ref, hovered] = useHover();
return (
<MyImageDiv ref={ref} className="row imageSpace">
{hovered && (
<div className="name">
<h1>
{header} <span className="likespan">{likes}</span>{" "}
<i onClick={() => addLike()} class="likeicon far fa-heart"></i>
</h1>{" "}
</div>
)}
<img className="image" alt="fall" src={src} />
</MyImageDiv>
);
};
You have your likes state set up in your gallery component. Even though you're mapping across each image you still assign the likes prop likes={likes} to the same state.
You'll want to move your likes state (and addLikes function) into your "MyImage" component, so each MyImage has its own separate bit of state.
You need to maintain a like variable in the data model itself since the likes property needs to be present for each item and not one as a whole
const Gallery = ({ initialData, initialDataSetTwo, initialDataSetThree }) => {
const [data, setData] = useState(initialData);
const [dataTwo, setDataTwo] = useState(initialDataSetTwo);
const [dataThree, setDataThree] = useState(initialDataSetThree);
const addLike = (index) => {
setData(prev => [...prev.slice(0, index), {...prev[index], likes: prev[index].likes + 1}, ...prev.slice(index+1)])
};
return (
<Wrapper>
<Styles className="row">
<Div className="col-xs-4">
{data.map((item, index) => (
<MyImage
key={item.id}
index={index}
src={item.fields.image.file.url}
header={item.fields.name}
likes={likes}
addLike={addLike}
/>
))}
</Div>
</Styles>
</Wrapper>
)
}
const MyImage = ({ src, index, header, likes, addLike }) => {
const [ref, hovered] = useHover();
return (
<MyImageDiv ref={ref} className="row imageSpace">
{hovered && (
<div className="name">
<h1>
{header} <span className="likespan">{likes}</span>{" "}
<i onClick={() => addLike(index)} class="likeicon far fa-heart"></i>
</h1>{" "}
</div>
)}
<img className="image" alt="fall" src={src} />
</MyImageDiv>
);
};