React Local Storage to target previously selected buttons - javascript

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.

Related

Is there a way to get all classes using document.querySelectorAll in reactjs and manipulate it?

I wanted to allow users to change the theme of the application by picking which theme they want the body's background color changes and all button colors. But the problem is that whenever I use document.querySelectorAll('.btn-theme-1').style.backgroundColor it tells me that it cannot read those properties.
I know of useRef() but in my case I am trying to select all buttons throughout the entire application. Not just one element in the current component. So I would like to know if there is a way to fix what I am attempting or if I am doing this the wrong way.
Here is the code for what I tried. This is my pick theme component:
import ColorThemes from '../data/ColorThemes';
import { useEffect } from 'react';
const PickTheme = () => {
const changeTheme = (c) => {
document.body.style.background = c.default || c.bgColor;
document.body.style.color = c.bodyColor;
document.querySelector('.bi-quote').style.color = c.buttonBg;
document.querySelectorAll('.text-color').forEach(el => el.style.color = c.fontColor)
document.querySelectorAll('.btn-theme-1').forEach(el => {
el.style.color = c.buttonColor;
el.style.backgroundColor = c.buttonBg;
});
};
useEffect(() => {
},[changeTheme]);
return(
ColorThemes.background.map(c => {
if(c.bgColor) {
return(
<button type="button" key={c.bgColor} className="btn btn-light me-2 p-3 rounded-5" onClick={() => changeTheme(c)} style={{backgroundColor: c.bgColor}}></button>
);
} else {
return(
<><br/><button type="button" key={c.default} className="btn btn-light me-2 mt-2 rounded-5" onClick={() => changeTheme(c)}>Default</button></>
);
}
})
);
};
export default PickTheme;
It successfully changes the bodys color and background color but not the other classes. I tried with and without useEffect and still receive the same issue.
If I comment out everything except the last selector, the buttons then change colors. So maybe it is conflicting or cannot change everything at once, for example:
const changeTheme = (c) => {
// document.body.style.background = c.default || c.bgColor;
// document.body.style.color = c.bodyColor;
// document.querySelector('.bi-quote').style.color = c.buttonBg;
// document.querySelectorAll('.text-color').forEach(el => el.style.color = c.fontColor)
document.querySelectorAll('.btn-theme-1').forEach(el => {
el.style.color = c.buttonColor;
el.style.backgroundColor = c.buttonBg;
});
};
This changes the buttons background and color after commenting out the other parts.
I know of useRef() but in my case I am trying to select all buttons throughout the entire application. Not just one element in the current component.
Using .querySelector or any other selector will select only those elements that are currently rendered, actually. So if you e.g. toggle the state and component re-renders with different elements, they will not be affected with your change, which will result in partially toggled theme for different elements.
You should either set a context, wrapping whole App or set a redux variable holding info which theme is currently selected. Then, you will be able to manipulate styles using e.g. theme in styled components: https://styled-components.com/docs/advanced#theming or just toggling classNames with css modules, basing on that variable.
You can use useRef() with a function that runs on each element and add them to it, let me dive deeper into it.
Let's first create a reference containing, for now, an empty array:
const myRef = useRef([])
Great, we now want to populate that.
It's going to be in two parts, first, let's make a function that will populates that array:
const addToMyRef = (element) => {
if (element && !myRef.current.includes(element)) {
myRef.current.push(element);
}
};`
Great, we now have a function that takes an element of the DOM as an argument, verifies that it exists and that it is not yet in our array, then adds it.
But now, when will it get triggered? Simply in the ref= attribute!
<button ref={addToMyRef}></button>
You'll now see that your reference is now a set of them, so you can create a reference per element, or maybe modify the code a little to makes it takes objects to have a all-in-one reference for each element of the dom. (We could imagine it being myRef.buttons/myRef.inputs...)

ReactJS: How To Get Input Value From The Prompt Box And Update It To Database

I'm building a MERN app, I want to let user edit the food name in the prompt box by clicking on the Edit button.
I was following the instructions in this link: [https://stackoverflow.com/questions/54993218/reactjs-using-alert-to-take-input-from-user]
The issue is when I click on the Edit button and type in the prompt then click OK, it will receive the null value for the first time, but it won't update the database.
And then when I click the Edit button again, without input anything to it then press OK, it will receive the value from the first time input and update it to database (like a delay).
What I want is: when click on the Edit button, it will display the prompt box and take the value from the prompt box and update to the database when the user clicks OK.
Is there any way I can fix this? Thank you everyone!
Here's my demo: gif
Here's my code:
function FoodListTable(props) {
/* Definition of handleClick in component */
const [newFoodName, setNewFoodName] = useState("")
const handleEdit = () => {
const enteredFood = prompt('Please enter your new food:')
setNewFoodName(enteredFood)
console.log(newFoodName)
if (newFoodName) {
Axios.put("https://mern-lefood.herokuapp.com/update", {
newFoodName: newFoodName,
id: props.val._id
})
}
}
return (
<button onClick={handleEdit}>Edit</button>
)
}
React this.setState, and useState does not make changes directly to the state object.
React this.setState, and React.useState create queues for React core to update the state object of a React component.
So the process to update React state is asynchronous for performance reasons. That’s why changes don’t feel immediate.
Try below code that's works !
function FoodListTable(props) {
/* Definition of handleClick in component */
const [newFoodName, setNewFoodName] = useState("");
const handleEdit = () => {
const enteredFood = prompt('Please enter your new food:');
setNewFoodName(enteredFood);
if (enteredFood) {
Axios.put("https://mern-lefood.herokuapp.com/update", {
newFoodName: enteredFood,
id: props.val._id
})
}
}
return (
<button onClick={handleEdit}>Edit</button>
)
}
For more detail Click here

JS sync and async onclick functions

I've been struggling with this issue lately.
I'm not sure if it has any connection to "sync/async" functions in JS. If it does, I would be more then thankful to understand the connection.
I've been making a simple component function:
There's a button "next","back" and "reset". Once pressing the matching button, it allows moving between linkes, according to button's type.
The links are an array:
const links = ["/", "/home", "/game"];
Here is the component:
function doSomething() {
const [activeLink, setActiveLink] = React.useState(0);
const links = ["/", "/home", "/game"];
const handleNext = () => {
setActiveLink((prevActiveLink) => prevActiveLink+ 1);
};
const handleBack = () => {
setActiveLink((prevActiveLink) => prevActiveLink- 1);
};
const handleReset = () => {
setActiveLink(0);
};
return (
<div>
<button onClick={handleReset}>
<Link className = 'text-link' to = {links[activeLink]}> Reset</Link>
</button>
<button onClick={handleBack}>
<Link className = 'text-link' to = {links[activeLink]}>Back</Link>
</button>
<button onClick={handleNext}>
<Link className = 'text-link' to = {links[activeLink]}>Next</Link>
</button>
</div>
When I'm trying to put the activeLink in the "to" attribute of Link, it puts the old value of it. I mean, handleNext/ handleReset/handleBack happens after the link is already set; The first press on "next" needs to bring me to the first index of the links array, but it stayes on "0".
Is it has to do something with the fact that setActiveLink from useState is sync function? or something to do with the Link?
I would like to know what is the problem, and how to solve it.
Thank you.
Your Links seem to be navigating to a new page?
If so, the React.useState(0) gets called each time, leaving you with the default value of 0.
Also your functions handleNext and handleBack aren't called from what I can see.

Targeting only the clicked item on a mapped component ( React quiz trivia App)

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.

retaining the state after I refresh the page

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.

Categories

Resources