The optimal way for creating wizard-like form in React - reactjs

I am creating a wizard-like form in React. I want it to display another select only when user selects an option in a previous dropdown. Currently my selection works, but I am using a lot of useState hooks here. As eventually I want to have around 10 selects, I think that as much as 10 useStates may not be the best option.
Can you please check out my code and give me some tips that would make it better/explain why such change would make it better?
Below I post my code, it works in codesandbox with bootstrap 4.2.1 and react-select 2.2.0:
import React, { useState } from "react";
import ReactDOM from "react-dom";
import Select from "react-select";
import "./styles.css";
import "bootstrap/dist/css/bootstrap.css";
const options1 = [
{ label: "1", value: 1 },
{ label: "2", value: 2 }
];
const options2 = [
{ label: "3", value: 3 },
{ label: "4", value: 4 }
];
const options3 = [
{ label: "5", value: 5 },
{ label: "6", value: 6 }
];
const options4 = [
{ label: "7", value: 7 },
{ label: "8", value: 8 }
];
const App = () => {
const [showSecond, setShowSecond] = useState(false);
const [showThird, setShowThird] = useState(false);
const [showFourth, setShowFourth] = useState(false);
const [showButton, setShowButton] = useState(false);
const renderSecond = () => {
if (showSecond) {
return (
<div className="form-group row mt-2">
<label className="col-4">Select 2</label>
<Select
onInputChange={updateSecond}
className="col-8"
options={options2}
/>
</div>
);
}
};
const renderThird = () => {
if (showThird) {
return (
<div className="form-group row mt-2">
<label className="col-4">Select 3</label>
<Select
onInputChange={updateThird}
className="col-8"
options={options3}
/>
</div>
);
}
};
const renderFourth = () => {
if (showFourth) {
return (
<div className="form-group row mt-2">
<label className="col-4">Select 4</label>
<Select
onInputChange={updateFourth}
className="col-8"
options={options4}
/>
</div>
);
}
};
const updateFirst = () => {
// update some data
setShowSecond(true);
};
const updateSecond = () => {
// update some data
setShowThird(true);
};
const updateThird = () => {
// update some data
setShowFourth(true);
};
const updateFourth = () => {
// update some data
setShowButton(true);
};
return (
<div className="container">
<div className="col-12">
<form className="form">
<div className="form-group row mt-2">
<label className="col-4">Select 1 </label>
<Select
className="col-8"
onInputChange={updateFirst}
options={options1}
/>
</div>
{renderSecond()}
{renderThird()}
{renderFourth()}
</form>
</div>
</div>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
As I am learning React, every suggestion will be very appreciated!

Related

Element type is invalid: expected a string (for built-in components) Check the render method of `UploadProduct`

Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
Check the render method of UploadProduct.
I don't know what i am missing here it's find to me if anyone got any error please let me know
UploadProduct.js
import React, { useState } from "react";
import { useHistory } from "react-router-dom";
import { Typography, Button, FormGroup, Input } from "#mui/material";
import FileUpload from "../../utils/FileUpload";
import Axios from "axios";
const { Title } = Typography;
const { TextArea } = Input;
const Continents = [
{ key: 1, value: "Africa" },
{ key: 2, value: "Europe" },
{ key: 3, value: "Asia" },
{ key: 4, value: "North America" },
{ key: 5, value: "South America" },
{ key: 6, value: "Australia" },
{ key: 7, value: "Antarctica" },
];
export const UploadProduct = () => {
const history = useHistory();
const [TitleValue, setTitleValue] = useState("");
const [DescriptionValue, setDescriptionValue] = useState("");
const [PriceValue, setPriceValue] = useState(0);
const [ContinentValue, setContinentValue] = useState(1);
const [Images, setImages] = useState([]);
const onTitleChange = (event) => {
setTitleValue(event.currentTarget.value);
};
const onDescriptionChange = (event) => {
setDescriptionValue(event.currentTarget.value);
};
const onPriceChange = (event) => {
setPriceValue(event.currentTarget.value);
};
const onContinentsSelectChange = (event) => {
setContinentValue(event.currentTarget.value);
};
const updateImages = (newImages) => {
setImages(newImages);
};
const onSubmit = (event) => {
event.preventDefault();
if (
!TitleValue ||
!DescriptionValue ||
!PriceValue ||
!ContinentValue ||
!Images
) {
return alert("fill all the fields first!");
}
const variables = {
title: TitleValue,
description: DescriptionValue,
price: PriceValue,
images: Images,
continents: ContinentValue,
};
Axios.post("/api/product/uploadProduct", variables).then((response) => {
if (response.data.success) {
alert("Product Successfully Uploaded");
history.push("/");
} else {
alert("Failed to upload Product");
}
});
};
return (
<div style={{ maxWidth: "700px", margin: "2rem auto" }}>
<div style={{ textAlign: "center", marginBottom: "2rem" }}>
<Title level={2}> Upload Travel Product</Title>
</div>
<FormGroup onSubmit={onSubmit}>
{/* DropZone */}
<FileUpload refreshFunction={updateImages} />
<br />
<br />
<label>Title</label>
<Input onChange={onTitleChange} value={TitleValue} />
<br />
<br />
<label>Description</label>
<TextArea onChange={onDescriptionChange} value={DescriptionValue} />
<br />
<br />
<label>Price($)</label>
<Input onChange={onPriceChange} value={PriceValue} type="number" />
<br />
<br />
<select onChange={onContinentsSelectChange} value={ContinentValue}>
{Continents.map((item) => (
<option key={item.key} value={item.key}>
{item.value}{" "}
</option>
))}
</select>
<br />
<br />
<Button onClick={onSubmit}>Submit</Button>
</FormGroup>
</div>
);
};
MainRoute.js
import { UploadProduct } from '../Components/UploadProduct/UploadProduct'
<Route exact path="/product/upload">
<UploadProduct/>
</Route>
Title and TextArea don't exist on Typography and Input
const { Title } = Typography;
const { TextArea } = Input;

How can we get the input text from multiple dynamic textareas in react hook?

I would like to get the text entered in the below input textarea created dynamically after the selection of persons from the dropdown boxes. Would like to get output into a json format:
Now it is getting value from the last displayed text area. Could someone please advise ?
Provide sample codesandbox link below
Expected output:
[
{name:"Bader", reason:"Good news, please add the some data"},
{name:"Crots", reason:"Great person"},
{name:"Dan", reason:"Simple look"}
]
App.js
import React, { useRef, useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { Link, useHistory } from "react-router-dom";
import Multiselect from "multiselect-react-dropdown";
const options = [
{ key: "Aaron", id: 1 },
{ key: "Bader", id: 2 },
{ key: "Crots", id: 3 },
{ key: "Dan", id: 4 },
{ key: "Elep", id: 5 },
{ key: "Pal", id: 6 },
{ key: "Quilt", id: 7 }
];
const App = () => {
const maxOptions = 3;
const [selectedOption, setSelectedOption] = useState([]);
const [nomRegister, setNomRegister] = useState([]);
const {
register,
handleSubmit,
watch,
formState: { errors }
} = useForm();
const handleTypeSelect = (e) => {
const copy = [...selectedOption];
copy.push(e);
setSelectedOption(copy);
};
const handleTypeRemove = (e) => {
const copy = [...selectedOption];
let index = copy.indexOf(e);
copy.splice(index, 1);
setSelectedOption(copy);
};
const sendNomination = () => {
console.log("Doesn't print all, what the heck: " + nomRegister);
};
return (
<div className="App">
<h1>Person selection</h1>
<div className="nomineeSelectBox">
<div id="dialog2" className="triangle_down1"></div>
<div className="arrowdown">
<Multiselect
onSelect={handleTypeSelect}
onRemove={handleTypeRemove}
options={selectedOption.length + 1 === maxOptions ? [] : options}
displayValue="key"
showCheckbox={true}
emptyRecordMsg={"Maximum nominees selected !"}
/>
</div>
</div>
<form onSubmit={handleSubmit(sendNomination)}>
<div className="nomineesSelectedList">
<h3>Selected Persons</h3>
{selectedOption.map((x, i) => (
<div key={i}>
<div className="row eachrecord">
<div className="column">
<label className="nomlabel">
{x[i].key} <b>>></b>
</label>
</div>
<input
required
type="textarea"
key={i}
id={i}
name={x[i].key}
className="nomineechoosed"
onChange={(e) => setNomRegister(e.target.value)}
/>
</div>
</div>
))}
<div className="row">
<div className="buttongroup">
<input id="Submit" type="submit" value="Submit" />
<input id="Cancel" type="button" value="Cancel" />
</div>
</div>
</div>
</form>
</div>
);
};
export default App;
Codesandbox link:
https://codesandbox.io/s/elastic-elbakyan-uqpzy?file=/src/App.js
After few modifications, I got a solution.
import React, { useRef, useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { Link, useHistory } from "react-router-dom";
import Multiselect from "multiselect-react-dropdown";
const options = [
{ key: "Aaron", id: 1 },
{ key: "Bader", id: 2 },
{ key: "Crots", id: 3 },
{ key: "Dan", id: 4 },
{ key: "Elep", id: 5 },
{ key: "Pal", id: 6 },
{ key: "Quilt", id: 7 }
];
const App = () => {
const maxOptions = 3;
const [selectedOption, setSelectedOption] = useState([]);
const [nomRegister, setNomRegister] = useState([{}]);
const {
register,
handleSubmit,
watch,
formState: { errors }
} = useForm();
// const onChange = (e) =>{
// e.persist();
// setNomRegister({ ...nomRegister, [e.target.id]: e.target.value });
// }
const handleTypeSelect = (e) => {
const copy = [...selectedOption];
copy.push(e);
setSelectedOption(copy);
};
const handleTypeRemove = (e) => {
const copy = [...selectedOption];
console.log(copy);
let index = copy.indexOf(e);
copy.splice(index, 1);
setSelectedOption(copy);
// immutating state (best practice)
const updateList = nomRegister.map((item) => {
return { ...item };
});
//delete the specific array case depends on the id
updateList.splice(index, 1);
setNomRegister(updateList);
};
const sendNomination = () => {
console.log(
"Doesn't print all, what the heck: " + JSON.stringify(nomRegister) // i did JSON.stringify just to see the console
);
};
const handleChange = (e, i) => {
const { name, value } = e.target;
// immutating state (best practice)
const updateList = nomRegister.map((item) => {
return { ...item };
});
//change the specific array case depends on the id
updateList[i] = { ...updateList[i], name: name, reason: value };
setNomRegister(updateList);
};
return (
<div className="App">
<h1>Person selection</h1>
<div className="nomineeSelectBox">
<div id="dialog2" className="triangle_down1"></div>
<div className="arrowdown">
<Multiselect
onSelect={handleTypeSelect}
onRemove={handleTypeRemove}
options={selectedOption.length + 1 === maxOptions ? [] : options}
displayValue="key"
showCheckbox={true}
emptyRecordMsg={"Maximum nominees selected !"}
/>
</div>
</div>
<form onSubmit={handleSubmit(sendNomination)}>
<div className="nomineesSelectedList">
<h3>Selected Persons</h3>
{selectedOption.map((x, i) => (
<div key={i}>
<div className="row eachrecord">
<div className="column">
<label className="nomlabel">
{x[i].key} <b>>></b>
</label>
</div>
<input
required
type="textarea"
key={i}
id={i}
name={x[i].key}
className="nomineechoosed"
onChange={(e) => handleChange(e, i)}
/>
</div>
</div>
))}
<div className="row">
<div className="buttongroup">
<input id="Submit" type="submit" value="Submit" />
<input id="Cancel" type="button" value="Cancel" />
</div>
</div>
</div>
</form>
</div>
);
};
export default App;
check Codesandbox

A method triggers a console.log from another component

When I click on bet now the function triggers a console.log from another component. betNow should group all the inputs from stake in one common array but when I click on it it renders the console log from stake and includes all the values that I typed into one array. Everything works but not as I wish. The parent component should display the common array with all the values. I do not understand why it is happening.Could anyone explain me why is reacting like that? Thanks in advance
Parent Component
import React, { useState } from 'react';
import Button from '#material-ui/core/Button';
import FilterMenu from "./selectButton";
import FetchRandomBet from "./fetchRandomBets";
function Betslip() {
const data = [
{
value: 0,
label: "No Filter"
},
{
value: 1,
label: "Less than two"
},
{
value: 2,
label: "More than two"
},
]
const [selectedValue, setSelectedValue] = useState(0);
const [allStakes, setAllStakes] = useState([]);
const handleChange = obj => {
setSelectedValue(obj.value);
}
const betNow = () => {
const stakes = localStorage.getItem("stakes");
const jsnStake = JSON.parse(stakes) || [];
setAllStakes([...allStakes, jsnStake]);
}
return (
<div className="betslip">
<div className="betslip-top">
<h1 className="text">BETSLIP</h1>
<p className="text-two">BET WITH US!</p>
<div>
<FilterMenu
optionsProp={data}
valueProp={selectedValue}
onChangeProp={handleChange}
/>
</div>
</div>
<div>
<FetchRandomBet
valueProp={selectedValue}
/>
</div>
<Button
onClick={betNow}
className="betnow"
variant="contained"
>
Bet Now!
</Button>
</div>
);
}
export default Betslip;
Child Component
import React, { useState, useEffect } from 'react';
function Stake() {
const [stakes, setStakes] = useState([]);
const addStake = (e) => {
e.preventDefault();
const newStake = e.target.stake.value;
setStakes([newStake]);
};
useEffect(() => {
const json = JSON.stringify(stakes);
localStorage.setItem("stakes", json);
}, [stakes]);
console.log(stakes)
return (
<div>
<form onSubmit={addStake}>
<input
style={{
marginLeft: "40px",
width: "50px"
}}
type="text"
name="stake"
required
/>
</form>
</div>
);
}
export default Stake;
You have this console.log in you function that will run every time the component is rendered, since it´s outside of any function:

Set title onSubmit based on current SELECT OPTION VALUE - react

I found this tutorial on how to create a react quizz app on youtube link to tutorial
I am trying to set the title based on the current Select Option Value when submitting the form.
Currently I managed to change the title only when a different option is selected.
import React, { useState, useEffect, useRef } from "react";
import "./App.css";
import axios from "axios";
import FlashcardList from "./components/FlashcardList";
function App() {
const [flashcards, setFlashcards] = useState([]);
const [categories, setCategories] = useState([]);
const [title, setTitle] = useState("General Knowledge");
const categoryEl = useRef();
const amountEl = useRef();
useEffect(() => {
axios.get("https://opentdb.com/api_category.php").then((res) => {
setCategories(res.data.trivia_categories);
});
}, []);
function decodeString(str) {
const textArea = document.createElement("textarea");
textArea.innerHTML = str;
return textArea.value;
}
function handleSubmit(e) {
e.preventDefault();
axios
.get("https://opentdb.com/api.php", {
params: {
amount: amountEl.current.value,
category: categoryEl.current.value,
},
})
.then((res) => {
setFlashcards(
res.data.results.map((questionItem, index) => {
const answer = decodeString(questionItem.correct_answer);
const options = [...questionItem.incorrect_answers, answer];
return {
id: `${index} - ${Date.now()}`,
question: decodeString(questionItem.question),
answer: answer,
options: options.sort(() => Math.random() - 0.5),
};
})
);
});
}
function getTitle(e) {
setTitle(e.target.options[e.target.selectedIndex].text);
}
return (
<>
<form className="header" onSubmit={handleSubmit}>
<div className="form-group">
<label htmlFor="category">Category</label>
<select id="category" ref={categoryEl} onChange={getTitle}>
{categories.map((category) => {
return (
<option value={category.id} key={category.id}>
{category.name}
</option>
);
})}
</select>
</div>
<div className="form-group">
<label htmlFor="amount">Number Of Questions</label>
<input
type="number"
id="amount"
min="1"
step="1"
defaultValue={10}
ref={amountEl}
/>
</div>
<div className="form-group">
<button className="btn">Generate</button>
</div>
</form>
<div className="container">
<h1 className="title">{title}</h1>
<FlashcardList flashcards={flashcards} />
</div>
</>
);
}
export default App;
Code
Live demo
You can set the category as soon the categories are fetched. Can just use the zeroth element to set the title.
useEffect(() => { axios.get("https://opentdb.com/api_category.php").then((res) => {
setCategories(res.data.trivia_categories);
setTitle(res.data.trivia_categories[0]);
});
}, []);

State Not getting Updated In Use Effect of React Hooks

This is the code I am using. Response is totally correct. But setState methods not updating the state and showing me the initial states that are "". What i am doing wrong? I am using them but this occurred for the very first time.
"React": "^16.2.0"
"react-dom": "^16.2.0"
const [name, setName] = useState("");
const [gender, setGender] = useState("");
const [skillSet, setSkillSet] = useState([]);
const [dob, setDob] = useState("");
useEffect(() => {
async function getStudent() {
try {
const { data } = await axios.get("/student/1");
const student = data[0];
const skills = data[1];
console.log(student[0]);
const { name: n, dob: d, gender: g } = student[0];
setName(n);
setGender(g);
setDob(d);
console.log("Logging Name", name);
console.log(n);
alert(name);
} catch (error) {}
}
getStudent();
}, []);
EDIT
Here is my complete component whose snippet i posted above. So can you guide me on this? Otherwise i have to return to class components
import React, { useState, useEffect } from "react";
import BaseInputComponent from "../Utils/InputComponentMaterialUi";
import BaseSelect from "../Utils/SelectComponentMaterialUi";
import instruments from "../InstrumentNames";
import BaseCheckBox from "../Utils/CheckBoxMaterialUi";
import axios from "axios";
const StudentProfile = () => {
const [name, setName] = useState("");
const [gender, setGender] = useState("");
const [skillSet, setSkillSet] = useState([]);
const [dob, setDob] = useState("");
useEffect(() => {
async function getStudent() {
try {
const { data } = await axios.get("/student/1");
const student = data[0];
const skills = data[1];
console.log(student[0]);
const { name: n, dob: d, gender: g } = student[0];
setName(n);
setGender(g);
setDob(d);
console.log("Logging Name", name);
console.log(n);
alert(name);
} catch (error) {
console.log(error);
console.log("Hi");
}
}
getStudent();
}, []);
return (
<div className="container" style={{ marginTop: "5rem" }}>
<form>
<div className="row">
<div className="col-md-6 col-12 col-lg-6">
<BaseInputComponent
value={name}
changeHandler={setName}
label={"Musician's Name"}
/>
</div>
<div className="col-md-6 col-12 col-lg-6">
<BaseInputComponent
changeHandler={setDob}
value={dob}
type="date"
label={"Date Of Birth"}
InputLabelProps={{
shrink: true
}}
/>
</div>
<div className="col-md-6 col-12 col-lg-6">
<BaseSelect
label={"Choose Gender"}
value={gender || ""}
changeHandler={setGender}
options={[
{ label: "Male", value: "Male" },
{ label: "Female", value: "Female" },
{ label: "Other", value: "Other" }
]}
/>
</div>
<div className="col-md-6 col-12 col-lg-6">
<BaseInputComponent label={"Osama Inayat"} />
</div>
</div>
<button type="submit" className="btn submit-button">
Update Profile Infomation
</button>
<br />
<br />
<br />
</form>
<div style={{ width: "100%", height: "2px", background: "gray" }}>
<h2 className="text-center">Select Instruments You Played</h2>
<form>
<div className="row">
{instruments.map((name, i) => (
<div className="col-md-3 col-6">
<BaseCheckBox
skillSet={skillSet}
setSkillSet={setSkillSet}
key={i}
label={name}
value={i}
/>
</div>
))}
</div>
</form>
</div>
<button onClick={() => console.log(skillSet)}>Show Object</button>
</div>
);
};
export default StudentProfile;
To expand upon my comment – a minimized example with the same idea (without async, since the version of Babel on Stack Overflow doesn't support it, sigh) works fine, so the issue is likely in the components you use within StudentProfile, not in your hooks:
function pretendAxios() {
return Promise.resolve({ data: [[{ name: "foo", dob: "1234-1234", gender: "bar" }], ["skill"]] });
}
const StudentProfile = () => {
const [name, setName] = React.useState("");
const [gender, setGender] = React.useState("");
const [skillSet, setSkillSet] = React.useState([]);
const [dob, setDob] = React.useState("");
React.useEffect(() => {
pretendAxios().then(({ data }) => {
const student = data[0];
const skills = data[1];
const { name: n, dob: d, gender: g } = student[0];
setName(n);
setGender(g);
setDob(d);
});
}, []);
return <div>{JSON.stringify({ name, gender, skillSet, dob })}</div>;
};
ReactDOM.render(<StudentProfile />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
<TextField
onChange={e => changeHandler(e.target.value)}
id="standard-required"
label={label}
defaultValue={value}
className={classes.textField}
margin="normal"
name={name}
id={id}
style={{ width: "100%" }}
{...rest}
/>
The above code i had written in me BaseInput component after changing defaultValue to value my problem solved thanks to #AKX for pointing out the problem

Resources