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.
Related
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}
Each button changes color when pressed. And I want them to stay that color every time the page reloads until a a localStorage.clear() is ran.
So I'm trying to re-apply the previously applied css with local storage. My idea is to store the Ids of each button, loop through Ids, and re-apply css. But it keeps throwing error: "Cannot read properties of null (reading 'style')". Another issue I seem to have is I lose my local storage on the 2nd page reload!
If you have a way to improve this or a better way to go about this please let me know! Thanks.
UPDATED Code
Here is a link to the most working version
https://codesandbox.io/s/festive-rui-vz7tf?file=/src/App.js
I added a "Delete History Btn".
The only issue now is after a reload and selecting another button, the local storage is completely erased with the new selection. What I need is the selections to stack until the user runs a localStorage.clear().
import React,{useState,useEffect} from 'react';
const App=()=> {
const [btns, setBtns] = useState([]);
useEffect(() => {
localStorage.setItem('BtnsClicked', JSON.stringify({ btns }));
}, [btns]);
const handleAcknowledge=(event)=>{
setBtns([...btns, event.target.id])
event.target.style.backgroundColor = "red"
event.target.innerHTML = "Clicked!"
}
const reSelectBtns=()=>{
const storedBtns = JSON.parse(localStorage.getItem('BtnsClicked'));
if (storedBtns.btns.length){
storedBtns.btns.forEach(btn=>{
console.log(document.getElementById(btn).style.backgroundColor = "red")
})
}
}
reSelectBtns()
return (
<div className="App">
<button id = "1" onClick={handleAcknowledge}>Acknowledge</button>
<button id = "2" onClick={handleAcknowledge}>Acknowledge</button>
<button id = "3" onClick={handleAcknowledge}>Acknowledge</button>
<button id = "4" onClick={handleAcknowledge}>Acknowledge</button>
</div>
);
}
export default App;
Checkout this sandbox link of mine https://codesandbox.io/s/blissful-marco-7wgon?file=/src/App.js
It addresses your problems.
I know this question has been asked before but the solutions posted there didn't correct my issue.
So I'm working with React and have a component. Everything else works fine, but now I'm trying to use a few useStates to get dynamic information as I intend to connect to an API/server further down the line.
I'm also using styled-components, which is why you'll see odd tag names. These work fine because I worked on them first and have them visible when I run npm start
The issue is that when I try to type something other than 0 in to this input, it refuses to update and gives me e.target is undefined
Here's where my error is occuring (on the e.target.value):
<InnerWrap flexColumn>
<AttributeFrame>
<EngravingInput
type= "text"
placeholder="20"
inputWidth="50px"
name="charSTR"
value={charAttributes.charSTR}
onChange={(e) => handle_attr_Change(e.target.value)}/>
<Spacer />
<>{((charAttributes.charSTR)-10)/2}</>
<>STR</>
</AttributeFrame>
</InnerWrap>
Here's the useState (Recently learned I could put multiple fields in a single useState):
const [charAttributes, set_CharAttributes] = useState(
{
charSTR: 0,
charSTRmod: 0
});
Then, here's the function that's supposed to fire on the onChange part of my element input:
//This is in my component definition, before the return block
const handle_attr_Change = (e) => {
const value = e.target.value;
set_CharAttributes(
{
...charAttributes,
[e.target.name]: value
}
);
The whole idea is that the charSTRmod value is supposed to update whenever the user types in a number. It's meant to calculate the attribute bonus for a strength attribute from DnD 5e.
Screengrab of the part:
Please could someone tell me what I did wrong?
You can pass the event itself e into handle_attr_Change instead of unpacking it:
<EngravingInput
...
onChange={handle_attr_Change}
/>
...
const handle_attr_Change = (e) => {
set_CharAttributes(
{
...charAttributes,
[e.target.name]: e.target.value
}
);
...
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-selectize component for customizable dropdown which allows users to add new options.
<Dropdown
options={myOptions}
value={selectedValue}
onValueChange={value => {
this.valueUpdated(emptyStringToNull(value));
}}
createFromSearch={this.createFromSearch}
/>
My createFromSearch and onValueChange functions are as below;
createFromSearch: function(options, search){
if (search.length === 0 || (options.map(function(option){
return option.label;
})).indexOf(search) > -1)
return null;
else {
return {'label': search, 'value': search};
}
},
onValueChange: function(text) {
// update the value in state
},
Everything works fine other than this small UI issue. It shows duplicate options soon after I click .
When I click anywhere in the screen it removes this duplicate layover and showing properly. Can anyone please suggest is it styling issue or any other thing I need to do?
I able to fix this issue by trying several things. I was overriding onValueChange method of the component and passed only the value to the actual onValueChange method as below;
const onValueChangeInDropdown = props => value => {
if (value) {
props.onValueChange(value.value);
} else {
props.onValueChange(null);
}
};
This cause the above styling issue since component couldn't find out item.newOption attribute. So solution is when adding newly created item for the option list add it as item.newOption = 'true' and pass the whole item object to onValueChange method.