im trying to create a react page with react-spring's parallax.Im using web api for data so when i use parallax tags im trying to set offset and scrollTo function's value.As you can see below;
class HomeComponent extends Component {
constructor(props) {
super(props);
this.state = {
list: [],
categoryCount: '',
}
}
componentDidMount() {
axios.get(`http://localhost:9091/api/category`)
.then(res => {
this.setState({ list: res.data, categoryCount: res.data.length })
});
}
so these are my declerations and web api call part, next part is render();
render() {
let i = 1
return <Parallax pages={this.state.categoryCount + 1} scrolling={true} vertical ref={ref => (this.parallax = ref)}>
<ParallaxLayer key={0} offset={0} speed={0.5}>
<span onClick={() => this.parallax.scrollTo(1)}>MAINPAGE</span>
</ParallaxLayer>
{this.state.liste.map((category) => {
return (
<ParallaxLayer key={category.categoryId} offset={i} speed={0.5}>
<span onClick={() => { this.parallax.scrollTo(i + 1) }}>{category.categoryName}</span>
{i += 1}
{console.log(i)}
</ParallaxLayer>
);
})}
</Parallax>
}
so in a part of this code, i am mapping the list for creating enough amount of parallax layer.But I can't manage the offset and this.parallax.scroll() 's values.These guys taking integer value for navigation to each other.
I tried the i and i+1 deal but it gets weird.First parallax works well it navigates the second page but after first page every page navigates me to the last page.I can't find a related question in stackoverflow so i need help on this one.Thanks for answers and sorry for my English.
After investigating the stackblitz, I see that the problem indeed was that the "i" value was increasing up to the list's length immediately, so wherever you clicked except for the first one, you were going to the last page.
I added a listIndex value to the map iteration, and incremented it there. You can follow the console.log statements, as well as the printed things on the screen.
This is a working solution but I'm more than certain there's a more elegant way to solve this (move it into a function etc).
render() {
let i = 1;
return <Parallax pages={this.state.liste.length + 1} scrolling={true} vertical ref={ref => (this.parallax = ref)}>
<ParallaxLayer key={0} offset={0} speed={0.5}>
<span onClick={() => this.parallax.scrollTo(1)}>MAINPAGE</span>
</ParallaxLayer>
{this.state.liste.map((category, listIndex) => {
return (
<ParallaxLayer key={category.categoryId} offset={listIndex + 1} speed={0.5}>
<span onClick={() => { this.parallax.scrollTo(listIndex + 1) }}>{category.categoryName} - test</span>
{listIndex += 1}
{console.log(listIndex)}
</ParallaxLayer>
);
})}
</Parallax>
}
Let me know if I can help with anything else.
Related
Struggle to make virtualized list scrolled to a particular row after content refresh. Scenario: there are few rows, user scrolls to somewhere in the list, and then triggers an event (e.g. presses a button in the app) that modifies the content of the list items. Ideally the user must see the same row at the top of the scroll view that was before the event occurred.
Here is the snippet from my code where I try to make it work with no success.
class MyList extends React.Component {
state = {
newScrollToIndex: undefined
}
// function triggered from outsided events
onCellContentChanged() {
const index = this.listRowIndex
// Don't know really which one to call exactlty
// Just called everything
this.cellMeasurerCache.clearAll()
this.listView.recomputeRowHeights()
this.listView.measureAllRows()
/*
* Tried this did not work at all.
* const off = this.listView.getOffsetForRow({ index })
* this.listView.scrollToPosition(off)
*/
// This two seem to work equivalently with 50% chance to work correctly.
// this.listView.scrollToRow(index)
this.setState({ newScrollToIndex: index })
}
renderInfiniteList({ height, width }) {
return (
<InfiniteLoader
isRowLoaded={this.isRowLoaded}
loadMoreRows={this.loadMoreRows}
rowCount={this.rowCount}
>
{({ onRowsRendered, registerChild }) => {
return (
<List
style={{ outline: 'none' }}
noRowsRenderer={() => (
<NoRows
loading={
this.state.loadingMoreRows || this.state.loadingFields
}
/>
)}
height={height}
width={width}
overscanRowCount={2}
rowCount={this.listViewRowCount}
rowHeight={this.cellMeasurerCache.rowHeight}
deferredMeasurementCache={this.cellMeasurerCache}
rowRenderer={this.rowRenderer}
scrollToIndex={this.state.newScrollToIndex}
onRowsRendered={(o) => {
onRowsRendered(o)
this.listRowIndex = o.startIndex
}}
ref={(listView) => {
registerChild(listView)
this.listView = listView
}}
/>
)
}}
</InfiniteLoader>
)
}
}
Any clarification on what is the proper way to make of this scenario to work is appreciated.
I am trying to delete cards that I have added to my container. These cards have a number in the center of them that is randomly generated. I have managed to set a unique key to these cards but everytime the add card button is added and a new card appears, the numbers are randomly generated again, so they change. This ends up creating duplicate unique keys and the browser console throws an error.
I am then trying to use those unique keys to be able to click the red X at the top right of the card so that I can delete that card. But since I can't keep the unique keys 'unique', I have been unable to do this.
My random number generator.
let getRandomNumber = function (min, max) {
let getRandom = Math.floor(Math.random() * max + min);
return getRandom;
};
The code for my cards.
const MainCard = () => {
return (
<Card>
<Button
onClick={() => removeCard()}
className="ui mini red basic icon button"
style={{
position: "absolute",
top: "0",
right: "0",
}}
>
<i
className="red x icon"
style={{
position: "relative",
top: "0",
right: "0",
}}
></i>
</Button>
<Card.Body>{getRandomNumber(0, 101)}</Card.Body>
</Card>
);
};
The container where the cards are populated each time the "Add Card" button is pressed.
<div className="card-container">
{cards.map((cardNumber, index) => (
<MainCard number={cardNumber} key={cardNumber.toString()} value={cardNumber} onRemove={() => removeCard(cardNumber)} />
))}
</div>
Remove Card
const removeCard = (cardIndex) => {
// Create a new Array without the item that you are removing.
const newCards = cards.filter((card, index) => index !== cardIndex);
setCards(newCards);
};
Not sure what I'm doing wrong. I've reviewed this article, https://reactjs.org/docs/lists-and-keys.html and feel I have a somewhat decent understanding of unique keys but I still can't quite figure it out.
Issue
You really shouldn't use random numbers as React keys as there isn't really a guarantee of uniqueness. This is especially true since you are only generating 101 random values, so as soon as you have 102 cards you're guaranteed to have a duplicate.
You are not really generating state or anything else that persists through rerenders. You are generating your content on-the-fly.
Solution
Since you want to add and remove cards "randomly" you need to store generated card data. You should also generate a unique id to go with the data that is being mapped.
Use an id generator to create unique ids that can be assigned to a card data element. I suggest something a little more robust, like uuid, but really anything the generates a unique, non-reapeating id is sufficient for this basic use-case.
Generate and store in component state the generated card data.
Pass the card element id to the delete handler to be used to filter cards by id.
Use the card element id as the React key when mapping cards.
Code
const generateId = (seed = 0) => () => seed++;
const getRandomNumber = function (min, max) {
let getRandom = Math.floor(Math.random() * max + min);
return getRandom;
};
const MainCard = ({ number, onRemove }) => {
return (
<div>
{number}
<button onClick={onRemove} className="ui mini red basic icon button">
<i
className="red x icon"
style={{
position: "relative",
top: "0",
right: "0"
}}
>
x
</i>
</button>
</div>
);
};
export default function App() {
const [cards, setCards] = useState([]);
const addCard = () => {
setCards((cards) => [
...cards,
{
id: generateId(),
number: getRandomNumber(0, 101)
}
]);
};
const removeCard = (id) => {
setCards((cards) => cards.filter((el) => el.id !== id));
};
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<button type="button" onClick={addCard}>
Add
</button>
<div className="card-container">
{cards.map((cardNumber, index) => (
<MainCard
number={cardNumber.number}
key={cardNumber.id}
onRemove={() => removeCard(cardNumber.id)}
/>
))}
</div>
</div>
);
}
Note: I did take some liberties with removing your custom components in rendering this simple example. It just needed to render a number value and delete button.
I figured it out. I was passing cardNumber instead of index in the key={}. I changed it over to index and the keys where unique. I still need to figure out how to delete now so that's the next step.
I have a function which creates 2 divs when changing the number of items correspondingly (say if we choose 5 TVs we will get 5 divs for choosing options). They serve to make a choice - only one of two possible options should be chosen for every TV, so when we click on one of them, it should change its border and background color.
Now I want to create a dynamic stylization for these divs: when we click on them, they should get a new class (tv-option-active) to change their styles.
For that purposes I used 2 arrays (classesLess and classesOver), and every time we click on one of divs we should remove a class if it's already applied to the opposite option and push the class to the target element - thus only one of options will have tv-option-active class.
But when I click on a div I do not get anything - when I open the document in the browser and inspect the elements, the elements do not even receive new class on click - however, when I console log the classes variable that should apply to an element, it is the way it should be - "less tv-option-active" or "over tv-option-active". I applied join method to remove the comma.
I checked the name of imported css file and it is ok so the problem is not there, also I applied some rules just to make sure the problem is not there and it worked when it's not dynamic (I mean no clicks are needed).
So my list of reasons causing that trouble seems to be not working.
I also tried to reorganize the code in order to not call a function in render return - putting mapping directly to render return, but this also didn't work.
Can anyone give me a hint why it is that?
Here is my code.
import React from 'react'
import { NavLink, withRouter } from 'react-router-dom'
import './TVMonting.css'
import PageTitle from '../../PageTitle/PageTitle'
class TVMontingRender extends React.Component {
state = {
tvData: {
tvs: 1,
under: 0,
over: 0,
},
}
render() {
let classesLess = ['less']
let classesOver = ['over']
const tvHandlers = {
tvs: {
decrease: () => {
if (this.state.tvData.tvs > 1) {
let tvData = {
...this.state.tvData,
}
tvData.tvs -= 1
this.setState({ tvData })
}
},
increase: () => {
if (this.state.tvData.tvs < 5) {
let tvData = {
...this.state.tvData,
}
tvData.tvs += 1
this.setState({ tvData })
}
},
},
}
const createDivs = () => {
const divsNumber = this.state.tvData.tvs
let divsArray = []
for (let i = 0; i < divsNumber; i++) {
divsArray.push(i)
}
return divsArray.map((i) => {
return (
<React.Fragment key={i}>
<div
className={classesLess.join(
' '
)}
onClick={() => {
const idx = classesOver.indexOf(
'tv-option-active'
)
if (idx !== -1) {
classesLess.splice(
idx,
1
)
}
classesLess.push(
'tv-option-active'
)
}}
>
Under 65
</div>
<div
className={classesOver.join(
' '
)}
onClick={() => {
const idx = classesLess.indexOf(
'tv-option-active'
)
if (idx !== -1) {
classesOver.splice(
idx,
1
)
}
classesOver.push(
'tv-option-active'
)
// classesOver.join(' ')
}}
>
Over 65
</div>
</React.Fragment>
)
})
}
return (
<div>
<button onClick={tvHandlers.tvs.decrease}>
-
</button>
{this.state.tvData.tvs === 1 ? (
<h1> {this.state.tvData.tvs} TV </h1>
) : (
<h1> {this.state.tvData.tvs} TVs </h1>
)}
<button onClick={tvHandlers.tvs.increase}>
+
</button>
{createDivs()}
</div>
)
}
}
export default withRouter(TVMontingRender)
CSS file is very simple - it just adds a border.
P.S. I know that with this architecture when I click on one of the divs all the divs will get tv-option-active class, but for now I just want to make sure that this architecture works - since I'm relatively new in React 🙂Thanks in advance!
Components won't have their lifecycle triggered if you are mutating a variable. You need a state for that purpose, which stores the handled data.
In your case you need some state to say which div has the active class, under or over. You can also abstract each rendered Tv to another Class component. This way you achieve independent elements that control their own class, rather than changing all others.
For that I created a Tv class, where I simplified some of the logic:
class Tv extends React.Component {
state = {
activeGroup: null
}
// this will update which group is active
changeActiveGroup = (activeGroup) => this.setState({activeGroup})
// activeClass will return 'tv-option-active' if that group is active
activeClass = (group) => (group === this.state.activeGroup ? 'tv-option-active' : '')
render () {
return (
<React.Fragment>
<div
className={`less ${ activeClass('under') }`}
onClick={() => changeActiveGroup('under')}
>
Under 65
</div>
<div
className={`over ${ activeClass('over') }`}
onClick={() => changeActiveGroup('over')}
>
Over 65
</div>
</React.Fragment>
)
}
}
Your TvMontingRender will be cleaner, also it's better to declare your methods at your class body rather than inside of render function:
class TVMontingRender extends React.Component {
state = {
tvData: {
tvs: 1,
under: 0,
over: 0,
}
}
decreaseTvs = () => {
if (this.state.tvData.tvs > 1) {
let tvData = {
...this.state.tvData,
}
tvData.tvs -= 1
this.setState({ tvData })
}
}
increaseTvs = () => {
if (this.state.tvData.tvs < 5) {
let tvData = {
...this.state.tvData,
}
tvData.tvs += 1
this.setState({ tvData })
}
}
createDivs = () => {
const divsNumber = this.state.tvData.tvs
let divsArray = []
for (let i = 0; i < divsNumber; i++) {
divsArray.push(i)
}
// it would be better that key would have an unique generated id (you could use uuid lib for that)
return divsArray.map((i) => <Tv key={i} />)
}
render() {
return (
<div>
<button onClick={this.decreaseTvs}>
-
</button>
{this.state.tvData.tvs === 1 ? (
<h1> {this.state.tvData.tvs} TV </h1>
) : (
<h1> {this.state.tvData.tvs} TVs </h1>
)}
<button onClick={this.increaseTvs}>
+
</button>
{this.createDivs()}
</div>
)
}
}
Note: I didn't change the key you are passing to Tv, but when handling an array that you manipulate somehow it's often better to pass an unique id identifier. There are some libs for that like uuid, nanoID.
When handling complex class logic, you may consider using libs like classnames, that would make your life easier.
I have a Gallery in my web application that I imported using 'React-Photo-Gallery.'
I am able to populate the gallery with images, and open up a photo carousel on click. How can I retrieve the index of the image clicked using the onClick method? activeIndex is set by default to 0, so upon clicking on any image, the first image in the array is displayed. I would like the clicked image to display in the carousel/Lightbox.
const mappingFunction = (img, index) => ({index, src: img.url, sizes: ["(min-width: 480px) 20vw,(min-width: 1024px) 25vw,25vw"], width: 4, height: 3});
const photosForGallery = images.map(mappingFunction)
After mapping the images, I display it like so:
<Gallery photos={photosForGallery} direction={"column"} columns={4} onClick={() => this.setState({ activeIndex: *use index here*, isOpen: true })} />
{isOpen && (
<Lightbox
mainSrc={urls[activeIndex]}
mainSrcThumbnail={urls[activeIndex]}
nextSrc={urls[(activeIndex + 1) % urls.length]}
nextSrcThumbnail={urls[(activeIndex + 1) % urls.length]}
prevSrc={urls[(activeIndex + urls.length - 1) % urls.length]}
prevSrcThumbnail={urls[(activeIndex + urls.length - 1) % urls.length]}
onCloseRequest={() => this.setState({ isOpen: false })}
onMovePrevRequest={() =>
this.setState({
activeIndex: (activeIndex + urls.length - 1) % urls.length
})
}
onMoveNextRequest={() =>
this.setState({
activeIndex: (activeIndex + 1) % urls.length
})
}
/>
)}
Where I'm having difficulty is accessing the index of the selected image. I've tried using the 'this' keyword, and even tried implementing a callback function. However (correct me if I'm wrong), it seems like the selected element on click is the Gallery itself, rather than my image of choice. Any input would be appreciated, as I'm new to React / JS.
Will continue to update my question as I do more research, though any input is appreciated.
For component Gallery onClick function:
Optional. Do something when the user clicks
a photo. Receives arguments event and an object containing the index,
Photos obj originally sent and the next and previous photos in the
gallery if they exist
http://neptunian.github.io/react-photo-gallery/api.html
So all you may have to do is as below in order to get an index:
<Gallery
photos={photosForGallery}
direction={"column"}
columns={4}
onClick={(e, { index }) => this.setState({ activeIndex: index, isOpen: true })} /> // <---- HERE
I'm building a conference website using three of these tabs (#speaker, #talks, #schedule). I think it is fair to want interactions between the tabs, here are a couple use cases that I cannot seem to solve.
From the #talks tab, I click on the bio hash - #johnsmith. This id exists within the page, but since I don't first switch tab to #speakers, nothing renders.
If I want to reference a specific talk and email someone the url: https://website.com#speaker_name the tabs won't open, and nothing but the tabs render.
The problem is compounded by the fact that when I click on an anchor tag href using a '#id', I must reload the page for it to fire.
I feel like there should be some way to pass a parameter when changing the tab or something... I'm in a tough spot because I'm rolling out code, but need this functionality badly.
Here is the actual open-source repo - https://github.com/kernelcon/website. The code I'm referencing can be found in src/pages/Agenda/.
Here is some example code.
Agenda.js
<Tabs defaultTab={this.state.defaultTab}
onChange={(tabId) => { this.changeTab(tabId) }}
vertical={vert}>
<TabList vertical>
<Tab tabFor="speakers">Speakers</Tab>
<Tab tabFor="talks">Talks</Tab>
<span>
<TabPanel tabId="speakers">
<Speakers />
</TabPanel>
<TabPanel tabId="talks">
<Talks />
</TabPanel>
</span>
</Tabs>
Talks.js
changeTab(id) {
window.location.reload(false);
}
getTalks() {
// Order Alphabetically
const talksOrdered = speakerConfig.sort((a,b) => (a.title > b.title) ? 1 : ((b.title > a.title) ? -1 : 0));
const talks = talksOrdered.map((ele, idx) => {
const twitterUrl = ele.twitter.replace('#', '');
return (
<div id={ele.talk_id}
key={idx}
className='single-talk'>
<div className='talk-title'>{ele.title}</div>
<div className='talk-sub-title'>
<div className='speaker-name'>
<a onClick={() => {this.changeTab(ele.speaker_id)}}
href={`#${ele.speaker_id}`}>{ele.speaker}</a>
</div>
...
I ended up accomplishing this by sending #tab_title/speaker_name, then adding a componentWillMount lifecycle method and function in the main tab file like below.
componentWillMount() {
const defaultTab = this.props.location.hash ? this.props.location.hash.split('#')[1] : 'schedule';
this.setState({
defaultTab: defaultTab
});
this.handleHashChange();
window.addEventListener('hashchange', this.handleHashChange);
}
handleHashChange = () => {
// given `#speakers/dave` now you have tabName='speakers', speakerHash='dave'
const [tabName, speakerHash] = window.location.hash.replace('#', '').split('/');
const tabNamesToWatchFor = [
'schedule',
'speakers'
];
if (tabNamesToWatchFor.includes(tabName)) {
this.setState({
defaultTab: tabName,
// pass this.state.speakerHash to <Speakers/> and use this for scrollIntoView in componentDidMount
speakerHash: speakerHash
});
}
}
Next, I went to the individual tab (in this case Speakers.js) and added a componentDidMount and componentDidUpdate method to help scroll to the speaker itself.
componentDidMount() {
this.handleScrollToSpeaker(this.props.speakerHash);
}
componentDidUpdate(prevProps) {
if (prevProps.speakerHash !== this.props.speakerHash) {
this.handleScrollToSpeaker(this.props.speakerHash);
}
}
handleScrollToSpeaker = hash => {
window.setTimeout(() => {
const ele = document.querySelector(`#${hash}`);
if (ele) {
ele.scrollIntoView({ block: 'start', behavior: 'smooth' });
}
}, 500)
}