Persisting state change with localStorage for each input - reactjs

So I have two radio button images, one checked and one not. I am trying to persist the change of state to view the corresponding image on button click for each of the inputs.
Please help.
Here's my code:
import React, { useState, useEffect } from 'react';
const Option = (props) => {
const img1 = <img alt='' src='/radio-active.png' className='radio__img' />;
const img2 = <img alt='' src='/radio-inactive.png' className='radio__img' />;
const [state, setState] = useState(false);
const handleStateChange = () => {
state === true ? setState(false) : setState(true);
};
useEffect(() => {
setState(JSON.parse(window.localStorage.getItem('state')));
}, []);
useEffect(() => {
window.localStorage.setItem('state', state);
}, [state]);
return (
<div className='option'>
<div className='radio'>
<button className='radio__button' onClick={handleStateChange}>
{state ? img1 : img2}
</button>
<p className='option__text radio__text'>{props.optionText}</p>
</div>
<button
className='button button--link'
onClick={(e) => {
props.handleDeleteOption(props.optionText);
}}
>
remove
</button>
</div>
);
};
export default Option;

All of your Option components are saving the state using the same key ("state"). You'll want each Option to have its own saved state. For each Option, add a new "optionName" property that is the key you want to use when saving the option's value to local storage.
// Change these:
window.localStorage.setItem('state', state);
setState(JSON.parse(window.localStorage.getItem('state')));
// To these:
window.localStorage.setItem(props.optionName, state);
setState(JSON.parse(window.localStorage.getItem(props.optionName)));

Related

There are two buttons and you need to change them alternately

I have two buttons. I can change its color by clicking on one button. And when you click on another button, change its color as well, and return the old color to the first button. Something like toggle. How can I implement such functionality in a react applicatio.
const [toggle, setToggle] = useState(false);
const toggleIt = () => {
setToggle(!toggle);
};
return (
<div>
<button onClick={toggleIt}>Button1</button>
<button onClick={toggleIt}>Button2</button>
)
somthing like this (codesandbox),
import classNames from "classnames";
import { useCallback, useState } from "react";
import "./styles.css";
export default function App() {
const [toggle, setToggle] = useState(false);
const toggleIt = useCallback(() => {
setToggle((toggle) => !toggle);
}, []);
return (
<div>
<button
onClick={toggleIt}
className={classNames({
"btn-act": toggle
})}
>
Btn A
</button>
<button
onClick={toggleIt}
className={classNames({
"btn-act": !toggle
})}
>
Btn B
</button>
</div>
);
}
const [toggle, setToggle] = useState(false);
const toggleIt = () => {
setToggle(!toggle);
};
return (
<div>
<button onClick={toggleIt} style={toggle ? {color: "blue"} : {color: "red"}}</button>
<button onClick={toggleIt} style={toggle ? {color: "pink"} : {color: "purple"}}</button>
</div>
)
Background
You can use the useEffect() hook to accomplish this feature depending on the button pressed. Just hold two states and flip them each time a different button is pressed, and with those two states you can use two separate functions to handle the onClick()'s.
The useEffect() hook automatically re-renders the component once any of the items in the dependency array at the end change, which will happen depending on the button pressed.
You can also directly set true/false values on your state variables with the second value that returns from useState(), and those state variables will automatically have their states updated without you manually assigning them.
There is very likely a better, more efficient way of doing it, but this is just a general guideline, if you will.
This is the code
const [toggleOne, setToggleOne] = useState(false);
const [toggleTwo, setToggleTwo] = useState(true);
const toggleFirst = () => {
setToggleOne(true);
setToggleTwo(false);
};
const toggleSecond = () => {
setToggleOne(false);
setToggleTwo(true);
};
useEffect(() => {
if (toggleOne) {
// Do something with first button pressed
} else if (toggleTwo) {
// Do something with second button pressed
}
}, [toggleOne, toggleTwo]);
return (
<div>
<button onClick={toggleFirst}>Button1</button>
<button onClick={toggleSecond}>Button2</button>
</div>
);

How set state for one individual component React when having multiple components

I have this Navbar with 3 tabs, and I managed to build a hook that sets a different style when clicked (changing its class); however, i don't know how target a state directly to just one tab. When clicked, all of then change their states. how I use the "this" in react in a case like this
const [isActive, setIsActive] = useState(false);
const handleClick = () => {
setIsActive(current => !current);
};
const setName = () => {
return (isActive ? 'true' : 'false');
}
return (
<NavStyled>
<div className="navbar-div">
<nav className="nav">
<p className={setName()} onClick={handleClick} >Basic</p>
<p className={setName()} onClick={handleClick} >Social</p>
<p className={setName()} onClick={handleClick} >Certificates</p>
</nav>
</div>
</NavStyled>
);
};
export default Navbar; ```
Navbar is a function component, not a hook.
You need to store either the currentTabIndex or currentTabName in the state.
var [currentTabName, setCurrentTabName] = useState('Basic');
handleClick=(evt)=> {
setCurrentName(evt.target.textContent);
};
['Basic','Social','Certificates'].map((tabName, i)=> {
let clazz = (tabName == currentTabName)?'active':'';
return <p key={tabName} className={clazz} onClick={handleClick} >{tabName}</p>
});

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] );
}}, []);

Drop down for a single element in a loop based on an id in react

I'm new to react and have an app that displays some data. I am using a map function to build one component multiple times. When a button is clicked inside of an element more data should be displayed but only in the clicked element. Currently, when I click a button in one element can toggle the display of the additional data for all element as well as store the unique id of the clicked element in a state. I am pretty sure that I need to filter the results and I have seen similar examples but I can't say that I fully understand them. Any tips or more beginner-friendly tutorials are greatly appreciated.
import React, { useState, useEffect } from 'react';
import '../style/skeleton.css'
import '../style/style.css'
export default function Body( student ) {
const [active, setActive] = useState({
activeStudent: null,
});
const [display, setDisplay] = useState(true)
useEffect(() => {
if (display === false) {
setDisplay(true)
} else {
setDisplay(false)
}
}, [active])
const handleClick = (id) => setActive({ activeStudent: id});
return (
<div>
{student.student.map((data) => {
const id = data.id;
return (
<div key={data.id} className="row border">
<div className="two-thirds column">
<h3>{data.firstName} {data.lastName}</h3>
{ display ?
<button onClick={() => handleClick(id)}>-</button>
:
<button onClick={() => handleClick(id)}>+</button> }
{ display ? <div>
<p>{data.addional} additonal data</p>
</div> : null }
</div>
</div>
)
})}
</div>
);
}
Change your code from:
{ display ? <div><p>{data.addional} additonal data</p></div> : null }
To:
{ active.activeStudent === id ? <div><p>{data.addional} additonal data</p></div> : null }

Passing OnChange Function using React for drop down

I have a payment component and custom dropdown component. I'm trying to pass down a function called handlePaymentImageChange from the parent (payment) to child (dropdown) so as to control the image change. However, it does not work well as I expect. What I'm trying to do is displaying the image based on the selection of the dropdown. In my case, if the value = 'Visa' -> render visa image only.
Details: https://codesandbox.io/s/serene-noether-s8pqc?file=/src/components/Payment/Payment.js
In my Payment.js
function Payment() {
const [paymentImage, setPaymentImage] = useState({
id: 0,
value: ""
});
const handlePaymentImageChange = (e) => {
const { name, value } = e.target;
setPaymentImage({
...paymentImage,
[name]: value
});
};
return (
<div className="payment-container">
<Dropdown
title="Select payment"
items={items}
multiSelect={false}
handlePaymentImageChange={handlePaymentImageChange}
/>
{/* render specifed image based on the selected choice */}
//REST RENDER CODE...
// for example, value = Visa -> render visa image only
</div>
);
In my Dropdown.js
import React, { useState } from "react";
import "./Dropdown.css";
function Dropdown({
title,
items = [],
multiSelect = false,
handlePaymentImageChange
}) {
const [open, setOpen] = useState(false);
const [selection, setSelection] = useState([]);
const [selectedValue, setSelectedValue] = useState(title);
//REST DROPDOWN TOGGLE FUNCTION
...
return (
<div className="dropdown-container">
// pass the item.value to change the Payment state, then render the correct image
{open && (
<ul className="dropdown-list">
{items.map((item) => (
<li
className="dropdown-list-item"
key={item.id}
onChange={() => handlePaymentImageChange(item.value)}
>
<button
type="button"
onClick={() => handleOnClick(item)}
value={item.value}
>
<span>{item.value}</span>
<span>{isItemInSelection(item) && "Selected"}</span>
</button>
</li>
))}
</ul>
)
}
</div>
);
}
export default Dropdown;
Any solution?
There are multiple issue,
In Dropdown component you should add eventListener for onClick not onChange.
Inside handlePaymentImageChange method you are using e.target.value for the value. But in your case e itself is the value. So you should write,
setPaymentImage({
...paymentImage,
value: e
});
When you are rendering the image there is no check. So check if value is "Visa" and render visa image and so on.
I have updated the code here please check.

Resources