no change despite using useState - reactjs

I have to make a dynamic survey where each next question comes from the previous one.
I want based on clicks to display/hide each question. I am doing this with style.display = "none" or "".
This the components' code.
As of now when I click the button, the isVisible array gets populated with next question's Id.
function refreshResults(questions, currentqId, val, isVisible) {
var nextqId = val.target.value;
// var currentqIdIdx = questions.findIndex(q => q.qID === currentqId)
var nextqIdIdx = questions.findIndex(q => q.qID === nextqId);
var ret = isVisible;
if (!isVisible.includes(nextqId)) {
ret.push(nextqId);
}
return ret;
}
const Questionnaire = () => {
const navigate = useNavigate();
let params = useParams();
let questionnaireId = params.questionnaireId;
// const [questionnaire, setQuestionnaire] = useState([]);
var questionnaire = test_input;
let questions = questionnaire.questions;
const [isVisible, setIsVisible] = useState([]);
useEffect(() => {
const setup = () => {
setIsVisible([questions[0].qID]);
// setIsVisible("");
}
setup();
}, [questions]);
return (
<div className="Questionnaire">
{
questions.forEach(question => {
if (isVisible.includes(question.qID)) {
question.style = { display: "" };
} else {
question.style = { display: "none" };
}
})
}
<>
{
questions.map((q) => (
<div className="card" style={q.style} key={q.qID}>
<div className="card-body" >
<h5 className="card-title">- {q.qtext}</h5>
<div className="buttons col">
{q.options.map(opt => (
<div key={opt.optID} className="form-check">
<input className="form-check-input" type="radio" name={`flexRadio${q.qID}`} value={opt.nextqID} onClick={clickValue => setIsVisible(refreshResults(questions, q.qID, clickValue, isVisible))} />
<label className="form-check-label" >
{opt.opttxt}
</label>
</div>
))
}
</div>
</div >
</div>
))
}
</>
<div className="submitButton"><input className="btn btn-primary" type="submit" value="Submit" /></div>
</div>
)
}```

Related

how to prevent useEffect to display validation erros when the page loading?

const Login = () => {
const [errors, setErrors] = useState({})
var newErrors = {}
const formValidation = () => {
if (name === "") {
newErrors.name = Name Can't Be Blanck
}
if (email === "") {
newErrors.email = <h1 className="text-red-800 text-center"> Email Address Is Required</h1>
} else if (/^\w+([\.-]?\w+)*#\w+([\.-]?\w+)*(\.\w{2,3})+$/.test(email)) {
newErrors.email = <h1 className="text-red-800 text-center">Email address is invalid</h1>
} else {
newErrors.email = <h1 className="text-green-800 text-center ">Email is Valid</h1>
}
if (password === "") {
newErrors.password = <h1 className="text-red-800 text-center">Password Is Required</h1>
} else if (!/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,15}$/.test(password)) {
newErrors.password = <h1 className="text-red-800 text-center">Invalid Password Format</h1>
} else {
newErrors.password = <h1 className="text-green-800 text-center ">Correct Password</h1>
}
setErrors(newErrors)
}
const handleSubmit = (e) => {
e.preventDefault()
formValidation()
console.log({ name, email, password })
}
useEffect(() => {
if (newErrors) {
formValidation()enter code here
}
}, [name, email, password])
return (
<div
className="login grid place-content-center
bg-gradient-to-r from-purple-900 via-purple-1000 to-blue-800 "
>
<form
className="card grid place-content-center h-96 w-96
"
onSubmit={handleSubmit}
>
<label htmlFor="">name:</label>
<input
type="text"
value={name}
placeholder="Enter Your Name"
onChange={(e) => setName(e.target.value)}
/>
{errors.name}
</form>
</div>
)
}
export default Login
The way i'd do it is to add a state called loading const {loading,setLoading}= useState(false)
set loading to true when , of course , loading.
and render errors conditionally using { loading? null : errors.name}

Check duplicates from a state hook in React

I'm trying to figure out on how to remove duplicates from an array of objects when entering multiple input skill tags. Maybe I am missing some key points here
const SkillsTags = ({ skillTags, setSkillTags, skill }) => {
const removeSkillTag = (i) => {
setSkillTags([...skillTags.filter((_, index) => index !== i)]);
};
const addSkillTag = (e) => {
e.preventDefault();
var updatedSkills = [...skillTags];
if (skill.current.value.trim().length !== 0) {
updatedSkills = [...skillTags, { SKILL_NAME: skill.current.value }];
}
setSkillTags(updatedSkills);
skill.current.value = "";
};
return (
<>
<label htmlFor="Skills">Skills:</label>
<div className="input-group">
<input
type="text"
placeholder="Enter Skills (Press Enter to add)"
onKeyPress={(e) => (e.key === "Enter" ? addSkillTag(e) : null)}
ref={skill}
/>
<button className="btn btn-outline-primary" onClick={addSkillTag}>
Add
</button>
</div>
<ul style={{ height: "12.5rem" }}>
{skillTags.map((val, index) => {
return (
<li key={index}>
{val.SKILL_NAME}
<button type="button" onClick={() => removeSkillTag(index)}>
Remove
</button>
</li>
);
})}
</ul>
</>
);
};
Demo here: https://codesandbox.io/s/add-skill-tags-nitthd?file=/src/SkillsTags.js
I think you are trying to filter duplicates, You can achieve this with simple Javascript.
const addSkillTag = (e) => {
e.preventDefault();
const isDuplicate = skillTags.some(function (item, idx) {
return item.SKILL_NAME === skill.current.value;
});
if (skill.current.value.trim().length !== 0) {
if (!isDuplicate) {
skillTags = [...skillTags, { SKILL_NAME: skill.current.value }];
}
}
setSkillTags(skillTags);
skill.current.value = "";
};

I am creating a frontend login form

The stack I am currently using is:
React, React-redux, styled-components, css3
I'm writing a natural responsive login form, and I'm trying to fix it with hooks while watching a course.
constructor(props) {
super(props);
this.state = {
isLogginActive: true
};
}
componentDidMount() {
//Add .right by default
this.rightSide.classList.add("right");
}
changeState() {
const { isLogginActive } = this.state;
if (isLogginActive) {
this.rightSide.classList.remove("right");
this.rightSide.classList.add("left");
} else {
this.rightSide.classList.remove("left");
this.rightSide.classList.add("right");
}
this.setState(prevState => ({ isLogginActive: !prevState.isLogginActive }));
}
render() {
const { isLogginActive } = this.state;
const current = isLogginActive ? "Register" : "Login";
const currentActive = isLogginActive ? "login" : "register";
return (
<div className="App">
<div className="login">
<div className="container" ref={ref => (this.container = ref)}>
{isLogginActive && (
<Login containerRef={ref => (this.current = ref)} />
)}
{!isLogginActive && (
<Register containerRef={ref => (this.current = ref)} />
)}
</div>
<RightSide
current={current}
currentActive={currentActive}
containerRef={ref => (this.rightSide = ref)}
onClick={this.changeState.bind(this)}
/>
</div>
</div>
);
}
}
const RightSide = props => {
return (
<div
className="right-side"
ref={props.containerRef}
onClick={props.onClick}
>
<div className="inner-container">
<div className="text">{props.current}</div>
</div>
</div>
);
};
export default App;
I'm working on the code from the lecture with hooks, but I'm starting to get confused about how to write a ref in the DOM.
export default function Modal() {
const dispatch = useDispatch();
const { isModal } = useSelector((state) => state.modal_Reducer);
const mainRef = useRef();
const rightRef = useRef();
const [isActive, setIsActive] = useState(true);
useEffect(() => {
rightRef.classList.add("right");
}, []);
const changeAuth = () => {
if (isActive) {
rightRef.classList.remove("right");
rightRef.classList.add("left");
} else {
rightRef.classList.remove("left");
rightRef.classList.add("rignt");
}
setIsActive(!isActive);
};
const onHideModal = () => {
dispatch(hideModal());
};
if (!isModal) {
return null;
}
const switchToSignup = isActive ? "Register" : "Login";
const switchToSignin = isActive ? "Login" : "Register";
return (
<ModalBackground>
<Main_Container>
<Auth_box ref={mainRef}>
{}
{}
</Auth_box>
<RightSide
ref={rightRef}
switchLogin={switchToSignin}
switcReg={switchToSignup}
onClick
/>
</Main_Container>
</ModalBackground>
It is a modal component that converts to the signup form while css animation effect occurs when the signup button is pressed in the login form.
I used useRef, but I think it's not right to use classList.add and .remove, so I need to fix it, but I'm not sure how to do it. Help.
useEffect(() => {
//rightRef.classList.add("right");
}, []);
const changeAuth = () => {
/* if (isActive) {
rightRef.classList.remove("right");
rightRef.classList.add("left");
} else {
rightRef.classList.remove("left");
rightRef.classList.add("rignt");
}
*/
setIsActive(!isActive);
};
and then
<RightSide
clsName={isActive? "right":"left"}
switchLogin={switchToSignin}
switcReg={switchToSignup}
onClick
/>
and update your component
const RightSide = props => {
return (
<div
className={`right-side ${props.clsName}`}
onClick={props.onClick}
>
<div className="inner-container">
<div className="text">{props.current}</div>
</div>
</div>
);
};
You can also explore https://www.npmjs.com/package/classnames npm package
useEffect(() => {
//rightRef.classList.add("right");
}, []);
const changeAuth = () => {
/* if (isActive) {
rightRef.classList.remove("right");
rightRef.classList.add("left");
} else {
rightRef.classList.remove("left");
rightRef.classList.add("rignt");
}
*/
setIsActive(!isActive);
};
and then
<RightSide
clsName={isActive? "right":"left"}
switchLogin={switchToSignin}
switcReg={switchToSignup}
onClick
/>
First, I set up a frame and kept working.
export default function Modal() {
const dispatch = useDispatch();
const { isModal } = useSelector((state) => state.modal_Reducer);
const mainRef = useRef();
const authRef = useRef();
const [isActive, setIsActive] = useState(true);
useEffect(() => {}, []);
const changeAuth = () => {
if (isActive) {
} else {
}
setIsActive(!isActive);
};
const onHideModal = () => {
dispatch(hideModal());
};
if (!isModal) {
return null;
}
const switchToSignup = isActive ? "Register" : "Login";
const switchToSignin = isActive ? "Login" : "Register";
return (
<ModalBackground>
<Main_Container ref={mainRef}>
<Auth_box>
{isActive && <Login authRef={authRef} />}
{!isActive && <Register authRef={authRef} />}
</Auth_box>
</Main_Container>
<RightSide
onSwitch={isActive ? "right" : "left"}
switchSignIn={switchToSignin}
switchRegister={switchToSignup}
onClick={changeAuth}
/>
</ModalBackground>
);
}
function RightSide(props) {
return (
<Right_Side
className={`right-side ${props.onSwitch}`}
ref={props.mainRef}
onClick={props.changeAuth}
>
<div className="inner-container">
<div className="text">{props.switchRegister}</div>
</div>
</Right_Side>
);
}
The problem is the login form conversion page, but the original uses ref and passes it as props.
login and register transfer current as ref.
But I don't understand why the container passes this.container.
original
export class Login extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="base-container" ref={this.props.containerRef}>
<div className="header">Login</div>
<div className="content">
<div className="image">
<img src={loginImg} />
</div>
<div className="form">
<div className="form-group">
<label htmlFor="username">Username</label>
<input type="text" name="username" placeholder="username" />
</div>
<div className="form-group">
<label htmlFor="password">Password</label>
<input type="password" name="password" placeholder="password" />
</div>
</div>
</div>
<div className="footer">
<button type="button" className="btn">
Login
</button>
</div>
</div>
);
}
my code
function Login(props) {
const dispatch = useDispatch();
const history = useHistory();
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [submited, setSubmited] = useState(false);
const [showPwd, setShowPwd] = useState(false);
const [alert, setAlert] = useState("");
const formInputValue = (key) => (e) => {
if (key === "EMAIL") setEmail(e.target.value);
if (key === "PASSWORD") setPassword(e.target.value);
console.log(e.target.value);
};
const onLogin = async (e) => {
e.preventDefault();
if (!email || !password) {
setAlert("이메일 주소와 비밀번호를 입력 하세요!");
}
let data = {
email: email,
password: password,
};
dispatch(loggedIn(data));
history.push("/");
};
return (
<PanelContainer ref={props.authRef}>
<h1>로그인</h1>
<PanelContent>
<Form onSubmit={onLogin}>
<InputGroup>
<label htmlFor="email">E-Mail</label>
<Input
type="text"
name="email"
value={email}
onChange={formInputValue("EMAIL")}
placeholder="E-mail"
/>
</InputGroup>
<InputGroup>
<label htmlFor="password">Password</label>
<Input
type={showPwd ? "text" : "password"}
name="password"
value={password}
onChange={formInputValue("PASSWORD")}
placeholder="Password"
/>
</InputGroup>
<SubmitBtn> 로그인 </SubmitBtn>
</Form>
</PanelContent>
</PanelContainer>
);
}
export default Login;

Update one element of big list without re render others elements in react hooks?

i want to optimize my react App by testing with a large list of li
Its a simple todo List.
By exemple, when click on a li, task will be line-through, and check icon will be green. This simple action is very slow with a large list because, the whole list is re render.
How to do this with React Hooks?
function App() {
const [list, setList] = useState([]);
const [input, setInput] = useState("");
const inputRef = useRef(null);
useEffect(() => inputRef.current.focus(), []);
//Pseudo Big List
useEffect(() => {
const test = [];
let done = false;
for (let i = 0; i < 5000; i++) {
test.push({ task: i, done });
done = !done;
}
setList(test);
}, []);
const handlerSubmit = (e) => {
e.preventDefault();
const newTask = { task: input, done: false };
const copy = [...list, newTask];
setList(copy);
setInput("");
};
const checkHandler = (e, index) => {
e.stopPropagation();
const copy = [...list];
copy[index].done = !copy[index].done;
setList(copy);
};
const suppression = (e, index) => {
e.stopPropagation();
const copy = [...list];
copy.splice(index, 1);
setList(copy);
};
const DisplayList = () => {
return (
<ul>
{list.map((task, index) => (
<Li
key={index}
task={task}
index={index}
suppression={suppression}
checkHandler={checkHandler}
/>
))}
</ul>
);
};
//JSX
return (
<div className='App'>
<h1>TODO JS-REACT</h1>
<form id='form' onSubmit={handlerSubmit}>
<input
type='text'
placeholder='Add task'
required
onChange={(e) => setInput(e.target.value)}
value={input}
ref={inputRef}
/>
<button type='submit'>
<i className='fas fa-plus'></i>
</button>
</form>
{list.length === 0 && <div id='noTask'>No tasks...</div>}
<DisplayList />
</div>
);
}
export default App;
Li component
import React from "react";
export default function Li(props) {
return (
<li
onClick={(e) => props.checkHandler(e, props.index)}
className={props.task.done ? "line-through" : undefined}
>
{props.task.task}
<span className='actions'>
<i className={`fas fa-check-circle ${props.task.done && "green"}`}></i>
<i
className='fas fa-times'
onClick={(e) => props.suppression(e, props.index)}
></i>
</span>
</li>
);
}
CodeSandbox here: https://codesandbox.io/s/sad-babbage-kp3md?file=/src/App.js
I had the same question, as #Dvir Hazout answered, I followed this article and made your code the changes you need:
function App() {
const [list, setList] = useState([]);
const { register, handleSubmit, reset } = useForm();
//Pseudo Big List
useEffect(() => {
const arr = [];
let done = false;
for (let i = 0; i < 20; i++) {
arr.push({ id: uuidv4(), task: randomWords(), done });
done = !done;
}
setList(arr);
}, []);
const submit = ({ inputTask }) => {
const newTask = { task: inputTask, done: false, id: uuidv4() };
setList([newTask, ...list]);
reset(); //clear input
};
const checkHandler = useCallback((id) => {
setList((list) =>
list.map((li) => (li.id !== id ? li : { ...li, done: !li.done }))
);
}, []);
const suppression = useCallback((id) => {
setList((list) => list.filter((li) => li.id !== id));
}, []);
//JSX
return (
<div className="App">
<h1>TODO JS-REACT</h1>
<form onSubmit={handleSubmit(submit)}>
<input type="text" {...register("inputTask", { required: true })} />
<button type="submit">
<i className="fas fa-plus"></i>
</button>
</form>
{list.length === 0 && <div id="noTask">No tasks...</div>}
<ul>
{list.map((task, index) => (
<Li
key={task.id}
task={task}
suppression={suppression}
checkHandler={checkHandler}
/>
))}
</ul>
</div>
);
}
Li component
import React, { memo } from "react";
const Li = memo(({ task, suppression, checkHandler }) => {
// console.log each time a Li component re-rendered
console.log(`li ${task.id} rendered.`);
return (
<li
onClick={(e) => checkHandler(task.id)}
className={task.done ? "line-through" : undefined}
>
{task.task}
<span className="actions">
<i className={`fas fa-check-circle ${task.done && "green"}`}></i>
<i className="fas fa-times" onClick={(e) => suppression(task.id)}></i>
</span>
</li>
);
});
export default Li;
You can check it live here
I know it's probably late for your question, but may help others ;)
You can use React.memo and wrap the Li component. This will cache the instances of the Li component based on shallow comparison. Read more in the docs
Otherwise, if you don't need the state in the container, you can keep it locally in the Li component and then it won't cause a whole list rerender.

Creating a clear function for budgeting app

I am working on creating a clear button that once clicked will clear all the transactions that have been added to the transaction list with localStorage. My button works but its buggy, once it gets clicked I get the following error about a separate function I have to get the balance. If I refresh the page afterwords though all the transactions will be cleared.
The error I am receiving ...
TypeError: amounts.reduce(...).toFixed is not a function
my component
import react, {useState, useEffect} from 'react'
import Transaction from './Transaction'
const Form = () => {
//initial state
const [transaction, setTransaction] = useState({
description: '',
amount: ''
})
const [list, setList] = useState(
JSON.parse(localStorage.getItem('list')) || []
)
const [balance, setBalance] = useState('')
const [income, setIncome] = useState(
JSON.parse(localStorage.getItem('income'))
)
const [expense, setExpense] = useState(JSON.parse(localStorage.getItem('expense')))
//updates based onChange value
const updateBalance = (e) => {
setTransaction({
...transaction,
[e.target.name]:
e.target.type == 'number' ? parseInt(e.target.value) : e.target.value
})
}
//identify if transaction is income/expense
const plusMinus = () => {
transaction.amount > 0
? setIncome(income + transaction.amount)
: setExpense(expense + transaction.amount)
}
// updates balance after transaction is added
const getBalance = () => {
const amounts = list.map(i => i.amount);
const money = amounts.reduce((acc, item) => (acc += item), 0).toFixed(2);
setBalance(money)
}
useEffect(() => {
getBalance()
localStorage.setItem('list', JSON.stringify(list))
localStorage.setItem('income', JSON.stringify(income))
localStorage.setItem('expense', JSON.stringify(expense))
}, [list])
//clear transaction list
const clearBudget = () => {
localStorage.clear();
}
const onSubmit = e => {
e.preventDefault();
setList([transaction, ...list])
plusMinus()
setTransaction({ description: '', amount: ''})
}
return (
<div>
<div className='totals'>
<h2 className='balance'> Current Balance </h2>
<h3> ${balance} </h3>
<h4> Income: ${income} Expense: ${expense} </h4>
</div>
< br />
< br />
< br />
<h2 className='trans-history'> Transaction History </h2>
{list.map(i => {
return (
<div className='trans'>
<ul key={i.description}>
{i.description} ${parseInt(i.amount)}
</ul>
</div>
)
})}
<br />
<br />
<h2 className='enter-item'> Enter an Item </h2>
<form onSubmit={onSubmit}>
<div>
<input
type='text'
className="input-trans"
placeholder='Enter Transaction'
value={Transaction.description}
name='description'
onChange={updateBalance}
>
</input>
</div>
<div>
<input
type='number'
className='input-trans'
placeholder='Enter Amount'
name='amount'
value={transaction.amount}
onChange={updateBalance}
>
</input>
</div>
<br/>
<div className='button-container'>
<button type='submit' className='button is-primary'> Submit </button>
<button className='button is-danger' onClick={clearBudget}> Clear </button>
</div>
</form>
</div>
)
}
export default Form
Looks like amounts.reduce is returning something that is not a number. You could check the type before to perform toFixed function.
E.g.:
const amounts = list.map((i) => i.amount).map(Number);
const money = amounts.reduce((acc, item) => (acc += item), 0)
if (typeof money === 'number') {
setBalance(money.toFixed(2))
} else {
setBalance(money)
}

Resources