Local storage and persistent state in React - reactjs

I want to make a like button where user can click and like something. When user clicks the button remains red even after refresh. How can i implement this?
I have this code. When i refresh the local storage gets reset. How can i get around this?
useEffect(() => {
setColor(window.localStorage.getItem('color'));
}, []);
useEffect(() => {
window.localStorage.setItem('color', color);
}, [color]);
const handleClick = () => {
setClicked(prevValue => !prevValue)
if(clicked){
setColor("red")
}else{
setColor("")
}
}
<div className="App">
<div className="container">
<button style={{backgroundColor: color}} onClick={handleClick} > +</button>
</div>
</div>

Try this approach. We need check twice localStorage first when the component mounting, second when we clicked the button. example
App.js
import { useState, useEffect } from "react";
const App = () => {
const [color, setColor] = useState("");
useEffect(() => {
const lS = window.localStorage.getItem("color");
if (lS) return setColor(lS);
localStorage.setItem("color", "");
}, []);
const handleClick = () => {
const lS = window.localStorage.getItem("color");
if (lS === "") {
localStorage.setItem("color", "red");
setColor("red");
}
if (lS !== "") {
localStorage.setItem("color", "");
setColor("");
}
};
return (
<div className="App">
<div className="container">
<button
style={{ backgroundColor: color }}
className="like-button"
onClick={handleClick}
>
+
</button>
</div>
</div>
);
};
export default App;

I have tried to duplicate this error in a sandbox. However, on my machine it works. Could it be that you have localStorage.removeItem('color') somewhere else in your project and gets called? Or maybe a problem with your browser. Here is the sandbox where it works: https://codesandbox.io/s/magical-shannon-cot7i?file=/src/App.js

I hope, it will work I have not tested it but I am sure it should work
useEffect(() => {
const storedColor = localStorage.getItem('color')
if(storedColor) {
setColor(storedColor);
}
}, []);
const handleClick = () => {
setClicked(prevValue => !prevValue)
if(clicked){
setColor("red");
localStorage.setItem('color', color);
}else{
setColor("")
}
}
return <div className="App">
<div className="container">
<button style={{backgroundColor: color}} onClick={handleClick} > + </button>
</div>
</div>

Related

React: I have a landing page, you click a button but the video background element is not auto playing with sound on mobile only. Desktop works

I have been building a react app. I have a landing page. It has a button that when I click sets the muted state to false. Which then the video loads in and is working on desktop.
But when it comes to mobile, the videos are not autoplaying and are staying frozen until I click the mute button again. Why would it work on desktop correctly but not mobile?
function App() {
const backgroundVideoElementRef = useRef<HTMLVideoElement>(null);
const [showElements, setShowElements] = useState(false);
const [muted, setMuted] = useState(false);
const [entered, setEntered] = useState(false);
const handleToggleMute = () => setMuted((current) => !current);
useEffect(() => {
if (entered) {
setTimeout(function () {
setShowElements(true);
}, 10000);
}
}, [entered]);
// hide the safari play button iOS safari low power mode
useEffect(() => {
if (!backgroundVideoElementRef.current) return;
backgroundVideoElementRef.current.addEventListener("loadeddata", () => {
const player = backgroundVideoElementRef.current;
setTimeout(() => {
const promise = player?.play();
if (promise?.then) {
promise
.then(() => {})
.catch(() => {
if (player) player.style.display = "none";
});
}
}, 0);
});
}, []);
useEffect(() => {
const onScroll = () => {
if (!backgroundVideoElementRef.current) return;
const rect = backgroundVideoElementRef.current.getBoundingClientRect();
const player = backgroundVideoElementRef.current;
if (rect.bottom < 0) {
player?.pause();
} else {
player?.play();
}
};
window.addEventListener("scroll", onScroll);
return () => window.removeEventListener("scroll", onScroll);
}, []);
return (
<StyledDiv className="home-page">
{entered ? (
<section className="home-page-hero">
<video
ref={backgroundVideoElementRef}
src={backgroundVideo}
className="background-video"
webkit-playsinline="true"
playsInline
loop
muted={muted}
autoPlay
/>
<div className="hero-container">
<div className="nav-bar">
<div onClick={handleToggleMute} className="mute-button">
<Mute isMuted={muted} />
</div>
<a
target="_blank"
rel="noopener noreferrer"
href="https://twitter.com"
>
<TwitterIcon className="twitter-icon" />
</a>
</div>
<div className="hero-content">
{showElements && (
<Fade
in={showElements}
style={{ transitionDelay: "100ms", transitionDuration: "2s" }}
>
<div>
<Menu />
</div>
</Fade>
)}
</div>
</div>
</section>
) : (
<section className="home-page-hero">
<div className="hero-content">
//Button to click is inside the Title component
<Title stateChanger={setEntered} />
</div>
</section>
)}
</StyledDiv>
);
}
export default App;

localStorage is saving my data but after refresh is reseting and empty it

I have a problem and I need you to help me understand it. I am using ReactJS and I am building a simple CRUD Todo App. I Want to store my todos in local storage.
The data is saved there and I can see it but after the refresh it is emptying my local storage.
What am I doing wrong?
Something that I notice is that from the first time when I open the app (first rendering), local storage is creating the storage space without adding a todo.
Could I have missed something in my code that makes it reset it or empty it when the page is rendered?
import React, { useState, useEffect } from "react";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import {
faCheck,
faPen,
faPlus,
faTrashCan,
} from "#fortawesome/free-solid-svg-icons";
import "./App.css";
import { faCircleCheck } from "#fortawesome/free-regular-svg-icons";
function App() {
const [todos, setTodos] = useState([]);
const [todo, setTodo] = useState("");
const [todoEditing, setTodoEditing] = useState(null);
const [editingText, setEditingText] = useState("");
useEffect(() => {
const json = window.localStorage.getItem("todos");
const loadedTodos = JSON.parse(json);
if (loadedTodos) {
setTodos(loadedTodos);
}
}, []);
useEffect(() => {
const json = JSON.stringify(todos);
window.localStorage.setItem("todos", json);
}, [todos]);
function handleSubmit(e) {
e.preventDefault();
const newTodo = {
id: new Date().getTime(),
text: todo,
completed: false,
};
setTodos([...todos].concat(newTodo));
setTodo("");
}
function deleteTodo(id) {
const updatedTodos = [...todos].filter((todo) => todo.id !== id);
setTodos(updatedTodos);
}
function toggleComplete(id) {
let updatedTodos = [...todos].map((todo) => {
if (todo.id === id) {
todo.completed = !todo.completed;
}
return todo;
});
setTodos(updatedTodos);
}
function submitEdits(id) {
const updatedTodos = [...todos].map((todo) => {
if (todo.id === id) {
todo.text = editingText;
}
return todo;
});
setTodos(updatedTodos);
setTodoEditing(null);
}
return (
<div className="App">
<div className="app-container">
<div className="todo-header">
<form onSubmit={handleSubmit}>
<input
type="text"
name="todo-input-text"
placeholder="write a todo..."
onChange={(e) => {
setTodo(e.target.value);
}}
value={todo}
/>
<button>
<FontAwesomeIcon icon={faPlus} />
</button>
</form>
</div>
<div className="todo-body">
{todos.map((todo) => {
return (
<div className="todo-wrapper" key={todo.id}>
{todo.id === todoEditing ? (
<input
className="edited-todo"
type="text"
onChange={(e) => setEditingText(e.target.value)}
/>
) : (
<p className={todo.completed ? "completed" : "uncompleted"}>
{todo.text}
</p>
)}
<div className="todo-buttons-wrapper">
<button onClick={() => toggleComplete(todo.id)}>
<FontAwesomeIcon icon={faCircleCheck} />
</button>
{todo.id === todoEditing ? (
<button onClick={() => submitEdits(todo.id)}>
<FontAwesomeIcon icon={faCheck} />
</button>
) : (
<button onClick={() => setTodoEditing(todo.id)}>
<FontAwesomeIcon icon={faPen} />
</button>
)}
<button
onClick={() => {
deleteTodo(todo.id);
}}
>
<FontAwesomeIcon icon={faTrashCan} />
</button>
</div>
</div>
);
})}
</div>
</div>
</div>
);
}
export default App;
You should be loading todos from localStorage on the Component mount if they are available in localStorage like this,
const loadedTodos = localStorage.getItem("todos")
? JSON.parse(localStorage.getItem("todos"))
: []; // new
const [todos, setTodos] = useState(loadedTodos); // updated
And then you don't have to mutate the state using setTodos(loadedTodos) in the useEffect.
Just remove this useEffect , from the code:
// that useEffect should be removed
useEffect(() => {
const json = window.localStorage.getItem("todos");
const loadedTodos = JSON.parse(json);
if (loadedTodos) {
setTodos(loadedTodos);
}
}, []);
You can check this in the working CodeSandbox as well.
I think your second useEffect is causing it to reset.
Move that the useEffect logic to a separate function.
And instead of calling setTodos, call that function, update the storage, and then call setTodos from that function.
If you call the setTodos function with a callback function and spread operator like this it should work:
useEffect(() => {
const json = window.localStorage.getItem("todos");
const loadedTodos = JSON.parse(json);
if (loadedTodos) {
// set local storage like this
setTodos( prevTodos => [...prevTodos, ...loadedTodos] );
}}, []);

How to update state for device width using Hooks in react

I am working on a React project, according to my scenario, a have button in my project and I have written two functions to change background color. First function will call if device width is less than or equal to 320px. Second function will call if device width is === 768px. but here the problem is when my device width is 320px when I click the button at that time the background color is changing to red here the problem comes now when I go to 768px screen then initially my button background color has to be in blue color, but it is showing red. to show button background color blue I have to update state for device size.
So someone please help me to achieve this.
This is my code
This is App.js
import React, { useState } from 'react';
import './App.css';
const App = () => {
const [backGroundColor, setBackGroundColor] = useState(null)
const [deviceSize, changeDeviceSize] = useState(window.innerWidth);
const changeBackGroundColorForMobile = () => {
if(deviceSize <= 320) {
setBackGroundColor({
backgroundColor: 'red'
})
}
}
const changeBackGroundColorForTab = () => {
if(deviceSize === 768) {
setBackGroundColor({
backgroundColor: 'green'
})
}
}
return (
<div className='container'>
<div className='row'>
<div className='col-12'>
<div className='first'>
<button onClick={() => {changeBackGroundColorForMobile(); changeBackGroundColorForTab() }} style={backGroundColor} className='btn btn-primary'>Click here</button>
</div>
</div>
</div>
</div>
)
}
export default App
If you have any questions please let me know thank you.
You're always running two functions. Don’t need that.
You’re updating the deviceSize only on the initial render. You have to update that in orientation change also.
Set the default colour always to blue.
import React, { useEffect, useState } from "react";
import "./App.css";
const App = () => {
const [backGroundColor, setBackGroundColor] = useState({
backgroundColor: "blue"
}); // Initialize bgColor with "blue"
const [deviceSize, changeDeviceSize] = useState(window.innerWidth);
useEffect(() => {
const resizeW = () => changeDeviceSize(window.innerWidth);
window.addEventListener("resize", resizeW); // Update the width on resize
return () => window.removeEventListener("resize", resizeW);
});
const changeBgColor = () => {
let bgColor = "blue";
if (deviceSize === 768) {
bgColor = "green";
} else if (deviceSize <= 320) {
bgColor = "red";
}
setBackGroundColor({
backgroundColor: bgColor
});
}; // Update the bgColor by considering the deviceSize
return (
<div className="container">
<div className="row">
<div className="col-12">
<div className="first">
<button
onClick={changeBgColor}
style={backGroundColor}
className="btn btn-primary"
>
Click here
</button>
</div>
</div>
</div>
</div>
);
};
export default App;
I would follow the previous advice to get the width and if you have lots of child components that rely on the width then I would suggest using the useContext hook so you don't have to keep passing the window data as a prop.
You can use useWindowSize() hook to get window width. And whenever width changes you can change background color by calling the functions in useEffect()
import { useState, useEffect } from "react";
// Usage
function App() {
const [backGroundColor, setBackGroundColor] = useState(null)
const { width } = useWindowSize();
useEffect(()=>{
if(width <= 320) {
changeBackGroundColorForMobile();
}
if(width === 768) {
changeBackGroundColorForTab()
}
}, [width])
const changeBackGroundColorForMobile = () => {
setBackGroundColor({
backgroundColor: 'red'
})
}
const changeBackGroundColorForTab = () => {
setBackGroundColor({
backgroundColor: 'green'
})
}
return (
<div className='container'>
<div className='row'>
<div className='col-12'>
<div className='first'>
<button style={backGroundColor} className='btn btn-primary'>Click here</button>
</div>
</div>
</div>
</div>
)
}
// Hook
function useWindowSize() {
// Initialize state with undefined width/height so server and client renders match
// Learn more here: https://joshwcomeau.com/react/the-perils-of-rehydration/
const [windowSize, setWindowSize] = useState({
width: undefined,
height: undefined,
});
useEffect(() => {
// Handler to call on window resize
function handleResize() {
// Set window width/height to state
setWindowSize({
width: window.innerWidth,
height: window.innerHeight,
});
}
// Add event listener
window.addEventListener("resize", handleResize);
// Call handler right away so state gets updated with initial window size
handleResize();
// Remove event listener on cleanup
return () => window.removeEventListener("resize", handleResize);
}, []); // Empty array ensures that effect is only run on mount
return windowSize;
}
You can use useEffect hook to add an event listener to window resize.
export default function App() {
const [bgClassName, setBgClassName] = useState("btn-primary");
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
function updateWidth() {
setWidth(window.innerWidth);
if(window.innerWidth === 768){
setBgClassName('btn-primary')
}
}
window.addEventListener("resize", updateWidth);
return () => window.removeEventListener("resize", updateWidth);
}, []);
const changeColor = () => {
if (window.innerWidth < 320) {
setBgClassName("btn-danger");
} else if (window.innerWidth === 768) {
setBgClassName("btn-success");
}
};
console.log(width);
return (
<div className="container">
<div className="row">
<div className="col-12">
<div className="first">
<button
onClick={() => changeColor()}
className={`btn ${bgClassName}`}
>
Click here
</button>
</div>
</div>
</div>
</div>
);
}

node.current is not a function

I am trying to create a function close modal when click outside but I am keep getting this error:
TypeError: node.current is not a function
Here is my following code in MemberCard.js:
const [modalStatus, setModalStatus] = useState(false);
const node = useRef(null);
const openModal = () => {
setModalStatus(!modalStatus);
};
const handleClick = (e) => {
if (node.current(e.target)) {
return;
}
// outside click
setModalStatus(false);
};
useEffect(() => {
document.addEventListener("mousedown", handleClick);
return () => {
document.removeEventListener("mousedown", handleClick);
};
}, []);
return (
<div className="member-card">
<div className="member-edit" onClick={openModal}>
<Symlink />
</div>
{modalStatus && (
<TeamStatusModal
active={modalStatus}
ref={node}
tab={tab}
member={member}
/>
)}
...
}
Here is my modal that I open after click:
const TeamStatusModal = (props) => {
const { active, tab, member, ref } = props;
console.log(ref);
return (
<div
className={`team-status-modal-container ${active ? "ACTIVE_CLASS" : ""}`}
>
<button className="status">
<ProfileIcon /> <span>View Profile</span>
</button>
<hr />
<button className="status">
<MessageIcon /> <span>Message Me</span>
</button>
</div>
);
};
How can I implement this feature?
In react, there are some good libraries that can help you with modals, one of them is called react-modal, you can give it a check.
If you want to implement a modal by yourself, we can follow some steps.
First we need to define a context, because the modal state needs to be accesed by more than one component or page in your app.
In the context, you could store the modal in a isModalOpen state, and add functions to manipulate it, such as openModal and closeModal. It really depends on the amount of features you want to add to this implementation.
Finally, you make the context globally accessible wrapping your app around a provider.
an example implementation
const ModalContext = createContext({})
export const ModalContextProvider = ({children}) => {
const [isModalOpen, setIsModalOpen] = useState(false)
const toggleModalState = () => {
setIsModalOpen(state => !state)
}
return <ModalContext.Provider value={{isModalOpen, toggleModalState}}>{children}<ModalContext.Provider>
}
export const useModal = () => {
return useContext(ModalContext)
}
Now the modal will be available globally

While on click on the span, how can we get the correct span name by using useRef()

I am getting the nominee_name as the last span name even after clicking on the right span element. How can I get the correct span name from here <span className="countname" key={data.nomineename} ref={nominee_name} onClick={handleClick}>{data.nomineename}</span>.
The above span is iterated based on the data received.
import React, { useRef, useEffect, useState } from "react";
import Axios from "axios";
const Dashboard = props => {
const [nominationCount, setNominationCount] = useState([]);
const [nameText, setNameText] = useState("");
let nominee_name = useRef(null);
const isMounted = useRef(false);
useEffect(() => {
isMounted.current = true;
return () => isMounted.current = false;
}, []);
useEffect(() => {
const fetchData = async () => {
try {
const res = await Axios.get('http://localhost:8000/service/nominationcount');
if (isMounted.current) {
setNominationCount(res.data);
console.log("Nomination count data from server :" + res.data);
}
} catch (e) {
console.log(e);
}
}
fetchData();
}, []);
const handleClick = () => {
setNameText(nominee_name.current.outerText);
}
return (
<div className="space_1 tile">
<h3>Nominations Count</h3>
<div className="grid-container">
{
!nominationCount.length && (<div className="nonominationdata">No nominations count to display !</div>)
}
{
nominationCount.map(data => (
<div key={data.id}>
<div onClick={() => {setOpen(!open); }} className="count badge" >
<span className="badgenumber" value={data.count} key={data.count}>{data.EmailCount}</span>
<span className="countname" key={data.nomineename} ref={nominee_name} onClick={handleClick}>{data.nomineename}</span>
</div>
</div>
))
}
</div>
</div>
<Modal
open={open}
onClose={() => {
setOpen(false);
}}
className={classes.modal}>
<form className={classes.form}>
<label className={classes.label}>Confirm winner {nameText}</label>
<input className={classes.submit} type="submit" value="Confirm" />
</form>
</Modal>
)
}
not sure you want to use ref here.
Just pass the name into your click handler:
(
<span className="countname" key={data.nomineename}
onClick={()=>setNameText(data.nomineename)}>{data.nomineename}</span>
)

Resources