Navigating through different pages with onClick & radio buttons - React.js - javascript

*Using react-router-dom and react.js
I have two different set of radio buttons. One set has 2 buttons while the other set has 3. I want to navigate to a new page whenever a user clicks on two buttons (one in each set). We have a total of six different outcomes, therefore 6 different pages to navigate to. It seems to work fine but with one problem: it only works when we click on a button for a second time. Example: Clicking on "Messages" & "Global" doesn't work initially and doesn't do anything but then if we click on a different button, then it navigates to the initial set of buttons we clicked.
Does anyone know how to fix this issue? Thank you.
import { useNavigate } from 'react-router-dom';
export default function DisplayHomeOptions() {
let navigate = useNavigate();
const [formData, setFormData] = React.useState({ location: "", selector: "" })
function handleChange(event) {
const { name, value, type, checked } = event.target
setFormData(prevFormData => {
return {
...prevFormData,
[name]: type === "checkbox" ? checked : value
}
})
}
function test() {
return (
formData.location === "National" && formData.selector === "Polls" ? navigate("/np") :
formData.location === "Global" && formData.selector === "Questions" ? navigate("/gq") :
formData.location === "Global" && formData.selector === "Polls" ? navigate("/gp") :
formData.location === "National" && formData.selector === "Messages" ? navigate("/nm") :
formData.location === "National" && formData.selector === "Questions" ? navigate("/nq") :
formData.location === "Global" && formData.selector === "Messages" ? navigate("/gm") : null
)
}
return (
<div>
<fieldset>
<legend>Option #1</legend>
<input
type="radio"
name="location"
value="Global"
onChange={handleChange}
onClick={test}
/>
<label htmlFor="Global">Global</label>
<input
type="radio"
name="location"
value="National"
onChange={handleChange}
onClick={test}
/>
<label htmlFor="National">National</label>
</fieldset>
<fieldset>
<legend>Option #2</legend>
<input
type="radio"
name="selector"
value="Messages"
onChange={handleChange}
onClick={test}
/>
<label htmlFor="Messages">Messages</label>
<input
type="radio"
name="selector"
value="Questions"
onChange={handleChange}
onClick={test}
/>
<label htmlFor="Questions">Questions</label>
<input
type="radio"
name="selector"
value="Polls"
onChange={handleChange}
onClick={test}
/>
<label htmlFor="Polls">Polls</label>
</fieldset>
</div>
)
}```

Issue
The issue is that React state updates aren't immediately processed, they are enqueued and asynchronously processed later. The formData state update from handleChange is not yet available when test function is called as the same time.
Solution
It seems you want the act of navigation to be an effect of selecting from the radio buttons. Move the test function logic into an useEffect hook to issue the imperative navigation.
Example:
export default function DisplayHomeOptions() {
const navigate = useNavigate();
const [formData, setFormData] = React.useState({
location: "",
selector: ""
});
useEffect(() => {
const { location, selector } = formData;
switch (true) {
case location === "National" && selector === "Polls":
navigate("/np");
break;
case location === "Global" && selector === "Questions":
navigate("/gq");
break;
case location === "Global" && selector === "Polls":
navigate("/gp");
break;
case location === "National" && selector === "Messages":
navigate("/nm");
break;
case location === "National" && selector === "Questions":
navigate("/nq");
break;
case location === "Global" && selector === "Messages":
navigate("/gm");
break;
default:
// ignore
}
}, [formData]);
function handleChange(event) {
const { name, value, type, checked } = event.target;
setFormData((prevFormData) => {
return {
...prevFormData,
[name]: type === "checkbox" ? checked : value
};
});
}
return (
<div>
<fieldset>
<legend>Option #1</legend>
<input
type="radio"
name="location"
value="Global"
onChange={handleChange}
/>
<label htmlFor="Global">Global</label>
<input
type="radio"
name="location"
value="National"
onChange={handleChange}
/>
<label htmlFor="National">National</label>
</fieldset>
<fieldset>
<legend>Option #2</legend>
<input
type="radio"
name="selector"
value="Messages"
onChange={handleChange}
/>
<label htmlFor="Messages">Messages</label>
<input
type="radio"
name="selector"
value="Questions"
onChange={handleChange}
/>
<label htmlFor="Questions">Questions</label>
<input
type="radio"
name="selector"
value="Polls"
onChange={handleChange}
/>
<label htmlFor="Polls">Polls</label>
</fieldset>
</div>
);
}
An optimization may be to declare a Map of target paths from the location and selector values as keys.
const navTarget = {
National: {
Messages: "/nm",
Polls: "/np",
Questions: "/nq",
},
Global: {
Messages: "/gm",
Polls: "/gp",
Questions: "/gq",
}
}
...
useEffect(() => {
const { location, selector } = formData;
if (location && selector) {
const target = navTarget[location][selector];
if (target) {
navigate(target);
}
}
}, [formData]);

onClick event fires before onChange event. To account for this, you could move navigation into the onChange event handler.

Related

"TypeError: object is not iterable (cannot read property Symbol(Symbol.iterator)) "

I am making controlled components in React and I am using states and seting states in forms. But this error occurs.
Here's the code of Quiz.js in which I am making this form for my Quiz app
`
import React from 'react'
export default function Quiz() {
const [questionForm, setQuestionForm] = React.useState(
{questionOne: "",
questionTwo: ""}
)
function handleChange(event) {
const [name , value , type , checked] = event.target
setQuestionForm(prev => {
return {
...prev,
[name]: type === 'checkbox' ? checked : value
}
})
}
return (
<div>
<div className = 'Question'>
<form>
<fieldset>
<legend>What is the capital of UK?</legend>
<label>
Paris
<input
id='Paris'
type="radio"
value='Paris'
name='questionOne'
onChange={handleChange}
checked={questionForm.questionOne === 'Paris'}
/>
</label>
<label>
Moscow
<input
id='Moscow'
type="radio"
value='Moscow'
name='questionOne'
onChange={handleChange}
checked={questionForm.questionOne === 'Moscow'}
/>
</label>
</fieldset>
</form>
<button className='check'>Check Answers</button>
</div>
</div>
)
}
`
I've checked the code again and again but I can't overcome the problem, what's wrong with my code?
replace
const [name , value , type , checked] = event.target
with this
const {name , value , type , checked} = event.target
Demo

.click() triggering twice and also results in having to click the browser back button twice to return to the previous page

The code below represents two sets of radio buttons. One button in each set must be clicked at all times. Clicking on one button in each set will navigate the user to a new page where they'll have the same radio buttons on that new page.
To achieve this I used a combination of useRef and .current.click() but the issue of doing it this way is that it triggers two clicks every time something on the page changes and that results in having to click the browser back button twice for each time a change is made. This wouldn't be very practical.
I've tried the defaultchecked property for radio buttons and it doesn't work properly for this case (needs to be manually clicked on again even though the visual shows it to be clicked on by default).
I know local Storage is another option but my coding skills are very limited and I'm not sure how to properly integrate it with my current code.
Any help would be greatly appreciated!
import React, { useEffect, useRef, useState } from 'react';
import { useNavigate } from 'react-router-dom';
export default function DisplayHomeOptions() {
let navigate = useNavigate();
const [formData, setFormData] = useState({ location: "", selector: "" })
const globalRef = useRef(null);
const messagesRef = useRef(null);
useEffect(() => {
globalRef.current.click();
messagesRef.current.click();
}, []);
function handleChange(event) {
const { name, value, type, checked } = event.target
setFormData(prevFormData => {
return { ...prevFormData, [name]: type === "checkbox" ? checked : value }
})
}
useEffect(() => {
const { location, selector } = formData;
if (location && selector) {
const target = navTarget[location][selector];
if (target) { navigate(target); }
}
}, [formData]);
const navTarget = {
National: { Messages: "/national/messages", Polls: "/national/polls", Questions: "/national/questions", },
Global: { Messages: "/global/messages", Polls: "/global/polls", Questions: "/global/questions", }
}
return (
<div>
<fieldset>
<legend>Option #1</legend>
<input type="radio" name="location" value="Global" onChange={handleChange} ref={globalRef} />
<label htmlFor="Global">Global</label>
<input type="radio" name="location" value="National" onChange={handleChange} />
<label htmlFor="National">National</label>
</fieldset>
<fieldset>
<legend>Option #2</legend>
<input type="radio" name="selector" value="Messages" onChange={handleChange} ref={messagesRef} />
<label htmlFor="Messages">Messages</label>
<input type="radio" name="selector" value="Questions" onChange={handleChange} />
<label htmlFor="Questions">Questions</label>
<input type="radio" name="selector" value="Polls" onChange={handleChange} />
<label htmlFor="Polls">Polls</label>
</fieldset>
</div>
)
}

What is the proper way to use radio in React? The button gets frozen once checked

I'm using state to control my component, and I'm not sure what part of the following code is causing the code to button to freeze once checked.
This is the constructor:
constructor(props) {
super(props);
this.state = {
firstName: '',
inPerson: false,
onlineMedium: true,
};
}
This function should handle change:
handleFormChange = (event) => {
const target = event.target;
if (target.name === "inPerson" || target.name === "onlineMedium") {
const value = !this.state[target.name]
const name = target.name;
this.setState({
[name]: value
});
}
else {
const value = target.value;
const name = target.name;
this.setState({
[name]: value
});
}
}
This renders the component:
render() {
return (
<>
<label className="tutor-add-label">
First name
<input
className="tutor-add-input"
type="text"
name="firstName"
value={this.state.firstName}
onChange={this.handleFormChange}
/>
</label>
<div className="medium">
<input
type="radio"
id="online"
name="onlineMedium"
checked={this.state.onlineMedium}
onChange={this.handleFormChange}
/>
<label htmlFor="online">online</label>
<input
type="radio"
id="person"
name="inPerson"
checked={this.state.inPerson}
onChange={this.handleFormChange}
/>
<label htmlFor="person">In person</label>
</div>
</>
)
}
Edit: As per the comment below, please let me know if there is another way to select/unselect radio that works better. I was following this http://react.tips/radio-buttons-in-react-16/
Update: It seems that the click doesn't happen (after the first click)for some reason. Does that seem to point in any direction?
This is what worked for me:
Changing the event handler from onChange to onClick and using the following to control state:
if (target.name === "onlineMedium" || target.name === "inPerson") {
if (event.target.checked && !this.state[target.name]) {
this.setState({
[target.name]: true,
})
}
else if (event.target.checked && this.state[target.name]) {
this.setState({
[target.name]: false,
})
}
}
Credit: it was inspired by this answer: https://stackoverflow.com/a/57147343/10813256

How to focus next input on pressing enter, in functional components? (TAB behavior)

I want to implement the TAB behavior, but with pressing ENTER.
Here is my attempt to solve it, but it doesn't work due to not using Refs.
My idea is to state in each input, which element should be focused next, and the Enter keyup event handler setting that value as the new focused field.
I've seen examples using useHook but I can't figure how to use it, without spamming a ton of useState's for each input.
Here is a Sandbox of the code below.
import React, { useState, useEffect } from "react";
function Form(props) {
// Holds the ID of the focused element
const [focusedElementId, setFocusedElementId] = useState("input-one");
// The actual focusing, after each re-render
useEffect(() => {
document.getElementById(focusedElementId).focus();
}, []);
useEffect(() => {
console.log("STATE:", focusedElementId);
}, [focusedElementId]);
// When Enter is pressed, set the focused state to the next element ID provided in each input
function handleKeyUp(e) {
e.which = e.which || e.keyCode;
if (e.which == 13) {
let nextElementId = e.target.attributes["data-focus"].value;
console.log(`HANDLER: Enter - focusing ${nextElementId}`);
setFocusedElementId(nextElementId);
}
}
return (
<div>
<div className="form-items" style={{ display: "flex" }}>
<div className="input-group">
<label>{"Input One"}</label>
<br />
<input
type={"text"}
id={"input-one"}
onKeyUp={handleKeyUp}
data-focus={"input-two"}
/>
</div>
<div className="input-group">
<label>{"Input Two"}</label>
<br />
<input
type={"text"}
id={"input-two"}
onKeyUp={handleKeyUp}
data-focus={"input-three"}
/>
</div>
<div className="input-group">
<label>{"Input Three"}</label>
<br />
<input
type={"text"}
id={"input-three"}
onKeyUp={handleKeyUp}
data-focus={"input-one"}
/>
</div>
</div>
</div>
);
}
export default Form;
I managed to get it working with refs.
Here's the sandbox.
It looks simple and clean enough, but I am still curious about your opinions.
import React from "react";
function Form() {
let one = React.createRef();
let two = React.createRef();
let three = React.createRef();
// When Enter is pressed, set the focused state to the next element ID provided in each input
function handleKeyUp(e) {
e.which = e.which || e.keyCode;
// If the key press is Enter
if (e.which == 13) {
switch (e.target.id) {
case "input-one":
two.current.focus();
break;
case "input-two":
three.current.focus();
break;
case "input-three":
one.current.focus();
break;
default:
break;
}
}
}
return (
<div>
<div className="form-items" style={{ display: "flex" }}>
<div className="input-group">
<label>{"Input One"}</label>
<br />
<input
type={"text"}
id={"input-one"}
onKeyUp={handleKeyUp}
ref={one}
/>
</div>
<div className="input-group">
<label>{"Input Two"}</label>
<br />
<input
type={"text"}
id={"input-two"}
onKeyUp={handleKeyUp}
ref={two}
/>
</div>
<div className="input-group">
<label>{"Input Three"}</label>
<br />
<input
type={"text"}
id={"input-three"}
onKeyUp={handleKeyUp}
ref={three}
/>
</div>
</div>
</div>
);
}
export default Form;
Here's a much more scalable version; just attach at the container element:
function onKeyDown(e) {
if (e.key === 'Enter') {
const fields =
Array.from(e.currentTarget.querySelectorAll('input')) ||
[]
const position = fields.indexOf(
e.target // as HTMLInputElement (for TypeScript)
)
fields[position + 1] && fields[position + 1].focus()
}
}
created this hook
import { useCallback, useEffect } from 'react'
export default function useFormTab() {
const keyDownHandler = useCallback((event: KeyboardEvent) => {
const target = event.target as HTMLButtonElement
if (event.keyCode === 13 && target.nodeName === "INPUT") {
var form = target.form;
var index = Array.prototype.indexOf.call(form, event.target);
// #ts-ignore
form.elements[index + 2].focus();
event.preventDefault();
}
}, []);
useEffect(() => {
document.addEventListener("keydown", keyDownHandler);
return () => document.removeEventListener("keydown", keyDownHandler);
}, [])
}

Checkbox don't show checked

I'm working with checkbox input. When I click on checbox, checkbox don't show checked but checkbox's value I still get. I use React JS
Simple checkbox
import React from 'react';
import callApi from './../../utils/apiCaller'
import { Link } from 'react-router-dom'
class ProductActionPage extends React.Component {
constructor(props) {
super(props);
this.state = {
id: '',
productStatus: ''
}
}
onChange = (e) => {
var target = e.target;
var name = target.name;
var value = target.type === 'checkbox' ? target.checked : target.value;
this.setState({
[name]: value
});
}
render() {
var statusCheckbox = this.state.productStatus === 'true' ? true : false;
return (
<div className="row">
<div className="col-xs-6 col-sm-6 col-md-6 col-lg-6">
<div className="form-group">
<label>Trang thai: </label>
</div>
<div className="checkbox">
<label>
<input type="checkbox" checked={statusCheckbox} name="productStatus" onChange={this.onChange} />
Con hang
</label>
</div>
<button type="submit" className="btn btn-primary">Luu lai</button>
<Link to="/product/list" className="btn btn-danger ml-5">Huy bo</Link>
</div>
</div>
);
}
}
How can I show checked checkbox?
this.state.productStatus is a boolean value, so your condition will always give you the false, because you are comparing Boolean === String.
Just change
var statusCheckbox = this.state.productStatus === 'true' ? true : false;
to
var statusCheckbox = this.state.productStatus ? true : false; //It doesn't make any sense
Or simply
var statusCheckbox = this.state.productStatus;
Or you can directly use this.state.productStatus,
<input
type="checkbox"
checked={this.state.productStatus}
name="productStatus"
onChange={this.onChange}
/>

Categories

Resources