i'm working on a project where i'm going to be displaying details and information about a certain book page by page inside a pager view as page components, the book contains 500+ pages so i can't just create 500 page components like that and insert them into the pager..what i thought is i can get a specific page, render its component, alongside the previous, and the next page only..and when the user swipes to the next/previous page i would change the component state, and have it re-render with the new 3 pages, the current one, the previous, and the next one. the logic in my head makes perfect sense, but it just won't work when i try to apply it.
can anyone help me, guide me to certain videos that explain this principal more? i feel like i'm missing something.
the code goes like this:
first i have the PagesContainer, here i will create the PagesDetails component(s) based on the current page, and having these pages in react-native-pager-view (you can suggest me a better option). for testing purpose only, i set the swipe end callback (onPageSelected) to increment the current page number state, which would then cause the component to re-render and create the new page component(s), that happens only when the user swipes to new page of course:
function PagesContainer({ currentPageNumber, setCurrentPageNumber }) {
const [pageComponents, setPageComponents] = useState([]);
useEffect(() => {
let compArr = [];
compArr.push(<PageDetails key="current" pageNumber={currentPageNumber} />);
if (currentPageNumber > 1) {
compArr.unshift(<PageDetails key="previous" pageNumber={currentPageNumber - 1} />)
}
if (currentPageNumber <= 500) {
compArr.push(<PageDetails key="next" pageNumber={currentPageNumber + 1} />)
}
setPageComponents(compArr);
}, [currentPageNumber])
return (<PagerView style={{ flex: 1 }}
initialPage={currentPageNumber == 1 ? 0 : 1}
layoutDirection={"rtl"}
onPageSelected={(PageSelectedEvent)=>{setCurrentPageNumber(currentPageNumber + 1)}}
>
{pageComponents.map(page => {
return page;
})}
</PagerView>)
}
and then here i have my PageDeatails component where i simply display texts and details of the page, i take the data from the bookData object which is imported at the very top of the code file:
function PageDetails({ pageNumber }) {
const [pageContent, setPageContent] = useState(null);
useEffect(() => {
setPageContent(bookData[pageNumber]["pageContent"]);
}, []);
return (
<View>
{pageContent && <View>
{pageContent.map(item => {
return (<Text>item</Text>)
})}
</View>
}
</View>
)
}
The logic makes perfect sense in my head but it just doesn't work when i test it..what am i missing? what am i doing wrong?
use the PagerView reference using useRef also store the page index and pass to initialPage get current page index from onPageSelected callback
like:
initialPage={currentPageIndex}
Related
i'm trying to develop an App with React using the Open trivia Api. I have mapped a button component (using material ui) to show the different answers for each question. I'm struggling now to target only the clicked one to apply a css property: if the answer is correct should become green, else red. The problem is the fact that once i click, all button become red or green. I tried to store the index in a state and compare the real index, but it doesn't work. here is my code:
in the main APP.js
const [clickedOne, setClickedOne] = useState({
clickedIndex: null,
});
useEffect(() => {
grabData();
}, []);
const handleClick = (choice, ke) => {
setChoice(choice);
if (choice === data.correct_answer) {
setIsCorrect(true);
} else {
setIsCorrect(false);
}
setClickedOne({ clickedIndex: ke });
grabData();
};
The mapped button inside the Render:
{answers.map((answer, index) => {
return (
<ContainedButtons
choice={handleClick}
answer={answer}
correct={data.correct_answer}
isCorrect={isCorrect}
key={index}
id={index}
clicked={clickedOne}
/>
);
})}
Inside the Button component:
const backStyle = () => {
if (clicked === id) {
if (isCorrect) {
return "green";
} else if (isCorrect === false) {
return "red";
} else {
return null;
}
}
};
return (
<div className={classes.root}>
<Button
style={{ backgroundColor: backStyle() }}
value={answer}
onClick={() => choice(answer, id)}
variant="contained"
>
{decodeURIComponent(answer)}
</Button>
When i check now inside the backstyle function if the clicked===id, now nothing happens anymore. Without that if check, i would have all buttons red or green.
Thank you guys for the help!
I have looked at your codesandbox demo, there are alot of other problems apart from the one your question is about.
First of all, each time you make a request to the API to fetch next question, you are making a request to get 10 questions instead of 1. API request URL contains a query parameter named amount which determines how many questions will be fetched on each request. Change its value to 1.
"https://opentdb.com/api.php?amount=1&encode=url3986"
Secondly, there is a lot of unnecessary code and unnecessary use of useState hook. You only need 2 things to be stored in the state, data and answers
const [data, setData] = useState({});
const [answers, setAnswers] = useState([]);
Now, coming to the original problem of detecting which button is clicked and correctly updating its background color.
To achieve the desired functionality, take following steps:
create couple of CSS classes as shown below
button.bgGreen {
background-color: green !important;
}
button.bgRed {
background-color: red !important;
}
pass a handleClick function from App component to ContainedButtons component. When a button is clicked, this click handler will be invoked. Inside the handleClick function, get the text and the button that was clicked using Event.target and depending on whether user answered correctly or not, add appropriate CSS class, created in step 1, on the button that was clicked.
Instead of using index as key for ContainedButtons in map function, use something that will be unique each time. This is needed because we want React to not re-use the ContainedButtons because if React re-uses the ContainedButtons component, then CSS classes added in step 2 will not be removed from the button.
Here's a working codesanbox demo of your app with the above mentioned steps.
In this demo, i have removed the unnecessary code and also changed the key of ContainedButtons inside map function to key={answer.length * Math.random() * 100}. You can change it to anything that will ensure that this key will be unique each time.
I am using React Virtualized to window my items list which has the following functionality
1) onClicking on any item, the user will be prompted with a details panel where he will be able to update the details of the item. And when he goes back to the list, he will be able to see the details in the item cell in the list. He can add a lot of details to make it bigger or reduce the size by removing the details
2) He can delete the item, or add another item to the list with certain details or without details.
CellMeasurer serves my requirement as dynamic height is supported. But I am having following issues with it
1) initially when my list mounts for the first time, first few items are measured and shown correctly but as soon as I scroll to the end, items get overlapped with each other.(positining isnt correct, I am guessing the defaultHeight is being applied to the unmeasured cells). This works fine as soon as the list is rerendered again.
2) Also, when I am updating the details of an item the list doesnt update with the new height adjustments
I am sure that somewhere my implementation is incorrect but have spent a lot of time hammering my head for it. Please let me know what can be done here to fix this
ItemView = props => {
const {index, isScrolling, key, style} = props,
items = this.getFormattedItems()[index]
return (
<CellMeasurer
cache={this._cache}
columnIndex={0}
isScrolling={isScrolling}
key={key}
rowIndex={index}
parent={parent}>
{({measure, registerChild}) => (
<div key={key} style={style} onLoad={measure}>
<div
onLoad={measure}
ref={registerChild}
{...parentProps}>
<Item
onLoad={measure}
key={annotation.getId()}
{...childProps}
/>
</div>
</div>
)}
</CellMeasurer>
)
}
renderDynamicListOfItems(){
return (<div>
<List
height={500}
ref={listComponent => (_this.listComponent = listComponent)}
style={{
width: '100%'
}}
onRowsRendered={()=>{}}
width={1000}
rowCount={props.items.length}
rowHeight={this._cache.rowHeight}
rowRenderer={memoize(this.ItemView)}
// onScroll={onChildScroll}
className={listClassName}
overscanRowCount={0}
/>
</div>
)
}
Also, I am manually triggering the remeasurement of my item in its componentDidUpdate like follows()
Component Item
...
componentDidUpdate() {
console.log('loading called for ', this.props.annotation.getId())
this.props.onLoad()
}
...
In the main parent I am recomputing the heights of the list every time the list has updated and triggering a forceupdate as follows
Component ParentList
...
componentDidUpdate() {
console.log("calling this parent recomputing")
this.listComponent.recomputeRowHeights()
this.listComponent.forceUpdateGrid()
}
...
I just faced the same issue and it turned out to be some layout updates in my Item component, that changed the height of the item after it has been measured by the CellMeasurer.
Fix was to pass down the measure function and call it on every layout update.
In my case it was
images being loaded
and text being manipulated with Twemoji (which replaces the emojis with images).
I also had problems getting the correct height from the cache after prepending some new list items when scrolling to the top of the list. This could be fixed by providing a keyMapper to the CellMeasurerCache:
const [cache, setCache] = useState<CellMeasurerCache>();
useEffect(() => {
setCache(new CellMeasurerCache({
keyMapper: (rowIndex: number, columnIndex: number) => listItems[rowIndex].id,
defaultHeight: 100,
fixedWidth: true,
}));
}, [listItems]);
Edit: forgot an important part - this is noticeable if you click the button next to Jeff A. Menges and check the console log.
The important part of the code is the "setFullResults(cardResults.data.concat(cardResultsPageTwo.data))" line in the onClick of the button code. I think it SHOULD set fullResults to whatever I tell it to... except it doesn't work the first time you click it. Every time after, it works, but not the first time. That's going to be trouble for the next set, because I can't map over an undefined array, and I don't want to tell users to just click on the button twice for the actual search results to come up.
I'm guessing useEffect would work, but I don't know how to write it or where to put it. It's clearly not working at the top of the App functional component, but anywhere else I try to put it gives me an error.
I've tried "this.forceUpdate()" which a lot of places recommend as a quick fix (but recommend against using - but I've been trying to figure this out for hours), but "this.forceUpdate()" isn't a function no matter where I put it.
Please help me get this button working the first time it's clicked on.
import React, { useState, useEffect } from "react";
const App = () => {
let artistData = require("./mass-artists.json");
const [showTheCards, setShowTheCards] = useState();
const [fullResults, setFullResults] = useState([]);
useEffect(() => {
setFullResults();
}, []);
let artistDataMap = artistData.map(artistName => {
//console.log(artistName);
return (
<aside className="artist-section">
<span>{artistName}</span>
<button
className="astbutton"
onClick={ function GetCardList() {
fetch(
`https://api.scryfall.com/cards/search?unique=prints&q=a:"${artistName}"`
)
.then(response => {
return response.json();
})
.then((cardResults) => {
console.log(cardResults.has_more)
if (cardResults.has_more === true) {
fetch (`https://api.scryfall.com/cards/search?unique=prints&q=a:"${artistName}"&page=2`)
.then((responsepagetwo) => {
return responsepagetwo.json();
})
.then(cardResultsPageTwo => {
console.log(`First Results Page: ${cardResults}`)
console.log(`Second Results Page: ${cardResultsPageTwo}`)
setFullResults(cardResults.data.concat(cardResultsPageTwo.data))
console.log(`Full Results: ${fullResults}`)
})
}
setShowTheCards(
cardResults.data
.filter(({ digital }) => digital === false)
.map(cardData => {
if (cardData.layout === "transform") {
return (
//TODO : Transform card code
<span>Transform Card (Needs special return)</span>
)
}
else if (cardData.layout === "double_faced_token") {
return (
//TODO: Double Faced Token card code
<span>Double Faced Token (Needs special return)</span>
)
}
else {
return (
<div className="card-object">
<span className="card-object-name">
{cardData.name}
</span>
<span className="card-object-set">
{cardData.set_name}
</span>
<img
className="card-object-img-sm"
alt={cardData.name}
src={cardData.image_uris.small}
/>
</div>
)
}
})
)
});
}}
>
Show Cards
</button>
</aside>
);
});
return (
<aside>
<aside className="artist-group">
{artistDataMap}
</aside>
<aside className="card-wrapper">
{showTheCards}
</aside>
</aside>
);
};
export default App;
CodesAndBox: https://codesandbox.io/embed/compassionate-satoshi-iq3nc?fontsize=14
You can try refactoring the code like for onClick handler have a synthetic event. Add this event Listener as part of a class. Use arrow function so that you need not bind this function handler inside the constructor. After fetching the data try to set the state to the result and use the state to render the HTML mark up inside render method. And when I run this code, I have also seen one error in console that child elements require key attribute. I have seen you are using Array.prototype.map inside render method, but when you return the span element inside that try to add a key attribute so that when React diffing algorithm encounters a new element it reduces the time complexity to check certain nodes with this key attribute.
useEffect(() => {
// call the functions which depend on fullResults here
setFullResults();
}, [fullResults])
// now it will check whether fullResults changed, if changed than call functions inside useEffect which are depending on fullResults
I am working on pagination in React . I am fetching data from online server through API . API was built in Loopback . I have some problem regarding next and previous button . I make logic for pagination if user want to render next page data it will click on next , If User want to load previous page data it click on previous button . With my logic Next button is working fine , it rendering the next page data if user click on next button but problem is that if user want to click on previous button it loading next page data not like previous page data. Somebody please help me how I can make this type of pagination if user click on next it must load next page data or user want to load previous page data it must load previous page with prev button .
Code
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
Item: 5,
skip: 0
}
this.handleClick = this.handleClick.bind(this);
}
urlParams() {
return `http://localhost:3001/meetups?filter[limit]=${(this.state.Item)}&&filter[skip]=${this.state.skip}`
}
handleClick() {
this.setState({skip: this.state.skip + 1})
}
render() {
return (
<div>
<a href={this.urlParams()}>Example link</a>
<pre>{this.urlParams()}</pre>
<button onClick={this.handleClick}>Change link</button>
</div>
)
}
}
ReactDOM.render(<Example/>, document.querySelector('div#my-example' ))
You have just write btnClick(e) in one way that adding the skip:skip+10 but if you need previous data it should subtract skip:skip-10
So the solution is
btnClick(e,type){
const Item=this.state.Item;
const skip=this.state.skip;
If(type === “next”){
this.setState({
Item,
skip:skip-10
},()=> this.getData())
} else {
this.setState({
Item,
skip:skip+10
})
}
}
// call function this.btnClick(e, “next”)
Or prev for pervious
If you look at the documentation for the Pagination component at https://react.semantic-ui.com/addons/pagination/ , You will see that you are passing this.btnClick into onClick={this.btnClick} when it should be passed into onPageChange={this.btnClick}
<Pagination
boundaryRange={0}
onPageChange={this.btnClick}
defaultActivePage={1}
ellipsisItem={null}
firstItem={null}
lastItem={null}
siblingRange={1}
totalPages={10}
/>
The parameters that are passed into onPageChange are as such:
onPageChange(event: SyntheticEvent, data: object)
It is not clear the difference between Item and Skip in state and what you are trying to accomplish with your btnClick function. It does not look like you are accounting for the possibility that you could click a numbered page on the pagination component instead of just the nextPage and PrevPage` buttons.
I'm going to assume Item indicates the current page you want to be on, in which case inside your btnClick function, you can grab the next active page from the data parameter (this will return the paginated number of the next page - whether you click that number directly or click nextPage).
btnClick(event, data){
const nextPage = data.activePage;
this.setState({item: nextPage});
}
I am trying to learn react myself.
when I hit favorites button which is the heart symbol changes the color.
but when I refresh the page it disappears.
so I researched and found the below link
How to maintain state after a page refresh in React.js?
after implementing now I am able to see the local storage in the developer tools application tab.
but after I refresh still the color is not retained.
when I debugged I found in getInitialState nothing is printing will that be problem
can you tell me how to fix it.
so that in future I will fix it myself.
providing my relevant code snippet and sandbox below.
all my code is in RecipeReviewCard.js
https://codesandbox.io/s/xrp56z04yq
getInitialState = () => {
var addFavirote = localStorage.getItem("AddFavirote") || 1;
console.log("getInitialState--->", addFavirote);
return {
addFavirote: addFavirote
};
//this.setState(state => ({ belowExpanded: !state.belowExpanded }));
};
<FavoriteIcon
style={{ display: this.state.addFavirote ? "none" : "" }}
onClick={e => {
console.log("favoriteEvent---.", e);
console.log(
"this.state.addFavirote---.",
this.state.addFavirote
);
localStorage.setItem("AddFavirote", !this.state.addFavirote);
this.setState({ addFavirote: !this.state.addFavirote });
console.log(
"!this.state.addFavirote---.",
!this.state.addFavirote
);
this.props.onAddBenchmark(this.props);
}}
/>
From the LocalStorage syntax documentation, you will need to serialize addFavorite to string to set to local storage. On componentDidMount when value is retrieved from localStorage, you can parse it back to the original content in getInitialState.
For example, you could
localStorage.setItem(JSON.stringify(!this.state.addFavorite)) //ie "true" || "false"
and get it back as
getInitialState = () => {
let fav = localStorage.getItem('AddFavorite');
let addFavorite = JSON.parse(fav || "true");
this.setState({ addFavorite });
}
PS: I recommend setting localStorage in componentWillUnmount if it wouldnt break things. Setting local storage and JSON serialization will affect performance.