and so I faced with a problem. I'm using react-form-hook with material-ui to create multi step form. the problem is when I stem forward react-form-hook forgets select value. the select component:
const Second_step_component = ({ register, errors }) => {
const [age, setAge] = React.useState('');
const handleChange = (event) => {
setAge(event.target.value);
};
return (
<FormControl sx={{ m: 1, minWidth: 120 }} error={Boolean(errors.Age)}>
<InputLabel id="demo-simple-select-helper-label">Age</InputLabel>
<Select
labelId="demo-simple-select-helper-label"
id="demo-simple-select-helper"
value={age}
label="Age"
{...register("Age", {required: "Age Is Required"})}
onChange={handleChange}
>
<MenuItem value={10}>Ten</MenuItem>
<MenuItem value={20}>Twenty</MenuItem>
<MenuItem value={30}>Thirty</MenuItem>
</Select>
<FormHelperText>{errors.Age?.message}</FormHelperText>
</FormControl>
);
}
export default Second_step_component
I understand that it's reverts back to its default state of '', but when its seems my knowledge is to limited to change useState to register("Age"), if someone could give me some hints, I would be very grateful
also including main stepper component:
const steps = ['House', 'Materials'];
export default function HorizontalLinearStepper() {
const [activeStep, setActiveStep] = React.useState(0);
const [skipped, setSkipped] = React.useState(new Set());
const {register, handleSubmit, formState: {errors}, trigger} = useForm()
const [state, setstate] = useState([{}]);
const isStepOptional = (step) => {
return step === 1;
};
const isStepSkipped = (step) => {
return skipped.has(step);
};
const handlesub = (data) =>{
setstate(data)
console.log(data)
}
const handleNext = async (e) => {
e.preventDefault()
let isNext = false
switch(activeStep){
case 0: isNext = await trigger(["Miestas", "Getvė", "Numeris"]);
break;
case 1: isNext = await trigger(["Age"]);
break;
}
if(isNext){
setActiveStep((prevActiveStep) => prevActiveStep + 1);
}
};
const handleBack = () => {
setActiveStep((prevActiveStep) => prevActiveStep - 1);
};
const handleSkip = () => {
if (!isStepOptional(activeStep)) {
// You probably want to guard against something like this,
// it should never occur unless someone's actively trying to break something.
throw new Error("You can't skip a step that isn't optional.");
}
setActiveStep((prevActiveStep) => prevActiveStep + 1);
setSkipped((prevSkipped) => {
const newSkipped = new Set(prevSkipped.values());
newSkipped.add(activeStep);
return newSkipped;
});
};
let Swithing = null
switch (activeStep) {
case 0:
Swithing = <First_step_component register={register} errors={errors}/>
break
case 1:
Swithing = <Second_step_component register={register} errors={errors}/>
break
}
const handleReset = () => {
setActiveStep(0);
};
return (
<Box sx={{padding: "2rem", display: "grid", placeContent: "center"}}>
<Box sx={{ width: '500px' }}>
<Stepper activeStep={activeStep}>
{steps.map((label, index) => {
const stepProps = {};
const labelProps = {};
if (isStepOptional(index)) {
labelProps.optional = (
<Typography variant="caption">Optional</Typography>
);
}
if (isStepSkipped(index)) {
stepProps.completed = false;
}
return (
<Step key={label} {...stepProps}>
<StepLabel {...labelProps}>{label}</StepLabel>
</Step>
);
})}
</Stepper>
{activeStep === steps.length ? (
<React.Fragment>
<Typography sx={{ mt: 2, mb: 1 }}>
All steps completed - you're finished
</Typography>
<Box sx={{ display: 'flex', flexDirection: 'row', pt: 2 }}>
<Box sx={{ flex: '1 1 auto' }} />
<Button onClick={handleReset}>Reset</Button>
</Box>
</React.Fragment>
) : (
<React.Fragment>
<Typography sx={{ mt: 2, mb: 1 }}>Step {activeStep + 1}</Typography>
<form id='step-form-sub' onSubmit={handleSubmit(handlesub)}>
<Box>{Swithing}</Box>
</form>
<Box sx={{ display: 'flex', flexDirection: 'row', pt: 2 }}>
<Button
color="inherit"
disabled={activeStep === 0}
onClick={handleBack}
sx={{ mr: 1 }}
>
Back
</Button>
<Box sx={{ flex: '1 1 auto' }} />
{/* {isStepOptional(activeStep) && (
<Button color="inherit" onClick={handleSkip} sx={{ mr: 1 }}>
Skip
</Button>
)} */}
{activeStep === steps.length - 1?
(<Button form="step-form-sub" type='submit'>submit</Button>)
:
(<Button type='button' onClick={handleNext}>next</Button>)
}
</Box>
</React.Fragment>
)}
</Box>
</Box>
);
}
It's because of the way you architected your stepper.
The state in the second step will be reverted to the original value once it's unmounted.
The solution is to move the state Into the wrapper component (e.g. if your wrapper component is App.jsx => move all stepper states into it)
Please have a look at the following stepper on code pen to understand:
React stepper
const App = () => {
const [acceptFirstTerms, setAcceptFirstTerms] = useState({
checked: false,
touched: false,
}),
[acceptSecondTerms, setAcceptSecondTerms] = useState({
checked: false,
touched: false,
}),
[acceptThirdTerms, setAcceptThirdTerms] = useState({
checked: false,
touched: false,
}),
[isSecondStepLoading, setIsSecondStepLoading] = useState(false);
const firstTermsHandler = () => {
setAcceptFirstTerms((prev) => ({ checked: !prev.checked, touched: true }));
};
const secondTermsHandler = () => {
setAcceptSecondTerms((prev) => ({ checked: !prev.checked, touched: true }));
};
const thirdTermsHandler = () => {
setAcceptThirdTerms((prev) => ({ checked: !prev.checked, touched: true }));
};
//for demo purposes only
const timeout = (ms) => {
return new Promise((resolve) => setTimeout(resolve, ms));
};
const secondStepAsyncFunc = async () => {
//it can be an API call
setIsSecondStepLoading(true);
await timeout(3000);
setIsSecondStepLoading(false);
console.log('second step clicked');
};
const stepperContent = [
{
label: 'Step 1',
content: (
<div>
<label>
<input
type="checkbox"
checked={acceptFirstTerms.checked}
onChange={firstTermsHandler}
/>{' '}
Accept first terms and conditions
</label>
</div>
),
isError: !acceptFirstTerms.checked && acceptFirstTerms.touched,
isComplete: acceptFirstTerms.checked,
},
{
label: 'Step 2',
content: (
<div>
<label>
<input
type="checkbox"
checked={acceptSecondTerms.checked}
onChange={secondTermsHandler}
/>{' '}
Accept second terms and conditions
</label>
</div>
),
clicked: () => secondStepAsyncFunc(),
isLoading: isSecondStepLoading,
isError: !acceptSecondTerms.checked && acceptSecondTerms.touched,
isComplete: acceptSecondTerms.checked,
},
{
label: 'Step 3',
content: (
<div>
<label>
<input
type="checkbox"
checked={acceptThirdTerms.checked}
onChange={thirdTermsHandler}
/>{' '}
Accept third terms and conditions
</label>
</div>
),
isError: !acceptThirdTerms.checked && acceptThirdTerms.touched,
isComplete: acceptThirdTerms.checked,
},
];
const submitStepper = () => {
console.log('submitted');
};
return (
<div className="container">
<h2>Default stepper</h2>
<Stepper stepperContent={stepperContent} submitStepper={submitStepper} />
<hr />
<h2>Inline stepper</h2>
<Stepper stepperContent={stepperContent} submitStepper={submitStepper} isInline />
<hr />
<h2>Vertical stepper</h2>
<Stepper stepperContent={stepperContent} submitStepper={submitStepper} isVertical />
</div>
);
};
Related
I want to add filter, search to ant design tables. Based on the documentation, we need to add some props to table column but it will be repeatative. Is there a way that we can creat a seperate component for the columns and filtering those columns?
I tried to develop that component but the column props' do not accept JSX.
I've done it in this way:
SearchHighliter:
const HighlighterWrapper = memo(({ searchWords, textToHighlight }) => (
<Highlighter
highlightStyle={{
backgroundColor: '#ffc069',
padding: 0,
}}
searchWords={searchWords}
autoEscape
textToHighlight={textToHighlight}
/>
))
FilterHook:
import { useRef, useState } from 'react'
import { Button, Input, Space } from 'antd'
import { SearchOutlined } from '#ant-design/icons'
const useTableFilter = () => {
const [searchText, setSearchText] = useState('')
const [searchedColumn, setSearchedColumn] = useState('')
const searchInput = useRef(null)
const handleSearch = (selectedKeys, confirm, dataIndex) => {
confirm()
setSearchText(selectedKeys[0])
setSearchedColumn(dataIndex)
}
const handleReset = (clearFilters, confirm) => {
clearFilters()
setSearchText('')
confirm()
}
const getColumnSearchProps = (dataIndex) => ({
filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters }) => (
<div
style={{
padding: 8,
}}
>
<Input
ref={searchInput}
placeholder="Search text"
value={selectedKeys[0]}
onChange={(e) => setSelectedKeys(e.target.value ? [e.target.value] : [])}
onPressEnter={() => handleSearch(selectedKeys, confirm, dataIndex)}
style={{
marginBottom: 8,
display: 'block',
}}
/>
<Space>
<Button
type="primary"
onClick={() => handleSearch(selectedKeys, confirm, dataIndex)}
icon={<SearchOutlined />}
size="small"
style={{
width: 90,
}}
>
Search
</Button>
<Button
onClick={() => clearFilters && handleReset(clearFilters, confirm)}
size="small"
style={{
width: 90,
}}
>
Reset
</Button>
</Space>
</div>
),
filterIcon: (filtered) => (
<SearchOutlined
style={{
color: filtered ? '#1890ff' : undefined,
marginRight: 10,
}}
/>
),
onFilterDropdownVisibleChange: (visible) => {
if (visible) {
setTimeout(() => searchInput.current?.select(), 100)
}
},
})
return [searchText, searchedColumn, getColumnSearchProps]
}
export default useTableFilter
Table:
const SummaryReport = () => {
const [data, setData] = useState([])
const [isLoading, setIsLoading] = useState(false)
const [searchText, searchedColumn, getColumnSearchProps] = useTableFilter()
const columns = [
{
title: () => <span style={{ paddingLeft: 8 }}>Name</span>,
key: 'name',
dataIndex: 'name',
className: 'no-padding-cell-report',
width: 250,
...getColumnSearchProps('name'),
onFilter: (value, record) => get(record, 'name').toString().toLowerCase().includes(value.toLowerCase()),
render: (_, record) => (
<div style={{ padding: 8 }}>
{searchedColumn === 'name' ? (
<HighlighterWrapper
searchWords={[searchText]}
textToHighlight={get(record, 'name') ? get(record, 'name').toString() : ''}
/>
) : (
get(record, 'name')
)}
</div>
),
},
]
....
}
All you need is just provide 2 functions to your column:
onFilter - here you describe your filter logic, if your data object is simple like in my case, the function also is trivial
render - you need to provide this function if you want to highlight text that the user inputs to filter input.
i stumbled into an issue i cant solve, i have an object 'customerDraft' which has nested object in it. i want to render every field plus the fields which are inside of 'customerDraft.metadata'.
my component looks like this:
const CustomerDetailEditModal = (props) => {
const {
open,
setOpen,
customerDraft,
customerProfileDraft,
setDraftCustomer,
setDraftProfile,
onUpdate
} = props;
const classes = useStyles();
const dispatch = useDispatch();
const [isPasswordHidden, setIsPasswordHidden] = useState(true);
// const [attributes, setAttributes] = useState({});
const projectId = useSelector(({ project }) => project.currentProject._id);
const generatedPassword = useSelector(({ customer }) => customer.password);
const isCurrentProjectCapstone = projectId === '4387564328756435';
const onModalCancel = () => {
setOpen(false);
if (isCurrentProjectCapstone) {
dispatch(removeItemFromCustomerDraftAction('password'));
}
};
const generatePassword = () => {
dispatch(getGeneratedPassword());
};
useEffect(() => {
if (!generatedPassword) return;
setDraftCustomer({
...customerDraft,
password: generatedPassword
});
// eslint-disable-next-line
}, [generatedPassword]);
console.log(customerDraft);
return (
<div>
<Modal
bodyStyle={{
fontSize: '12px',
height: 500,
margin: '0 auto'
}}
centered
footer={
<div
style={{
display: 'flex',
justifyContent: 'flex-end'
}}>
<CButton
htmlType="submit"
onClick={(e) => {
setOpen(false);
e.preventDefault();
}}
size="large"
type="secondary">
Cancel
</CButton>
<CButton
htmlType="submit"
onClick={onUpdate}
size="large"
type="primary"
// disabled={!isSaveEnabled}
>
Save
</CButton>
</div>
}
onCancel={onModalCancel}
title={
<span
style={{
fontSize: '24px',
fontWeight: 700,
lineHeight: '24px'
}}>
Edit User
</span>
}
visible={open}
width={customerProfileDraft ? 770 : 385}>
<form className={classes.form} id="customer-edit-form">
<div className={classes.wrapperDiv}>
{Object.entries(customerDraft).map((item, i) => {
if (customerDraft.fullName) {
if (restrictedData.includes(item[0] || item[0].toLowerCase().includes('id'))) {
return false;
}
}
if (restrictedData.includes(item[0]) || item[0].toLowerCase().includes('id')) {
return false;
}
return (
<CStandardInput
key={i}
allowClear
defaultValue={item[1]}
disableUnderline
formclasses={{ root: classes.root }}
htmlFor={`standard-customer-edit-${item[0]}`}
id={`standard-customer-edit-${item[0]}`}
label={item[0]}
onChange={(event) => {
setDraftCustomer({
...customerDraft,
fullName: event.target.value
});
setDraftProfile({
...customerProfileDraft,
fullName: event.target.value
});
}}
size="large"
/>
);
})}
{isCurrentProjectCapstone && (
<div className={classes.passwordWrapper}>
<CStandardInput
adornment={
<>
<button
className={classes.buttonSvg}
onClick={() => {
navigator.clipboard.writeText(customerDraft.password || '');
}}
style={{
marginRight: '5px'
}}
type="button">
<img alt="copy password" src={copyIcon} />
</button>
<button
className={classes.buttonSvg}
onClick={() => setIsPasswordHidden(!isPasswordHidden)}
type="button">
<img
alt="toggle password visibility"
src={isPasswordHidden ? crossedEyeIcon : eyeIcon}
/>
</button>
</>
}
disableUnderline
formclasses={{ root: classes.root }}
htmlFor="standard-input-user-password"
id="standard-input-user-password"
label="Password"
onChange={(e) => setDraftCustomer({ ...customerDraft, password: e.target.value })}
size="large"
type={isPasswordHidden ? 'password' : 'text'}
value={customerDraft.password || ''}
width="true"
/>
<CButton
onClick={generatePassword}
type="primary"
xstyle={{
borderRadius: '12px',
margin: '16px 0px 0px 16px'
}}>
Generate
</CButton>
</div>
)}
</div>
</form>
</Modal>
</div>
);
};
export default CustomerDetailEditModal;
notice how metdata field is rendered? i want to use recursion to output every field which metadata contains,
i know recursion but what i cant seem to figure out is where should this component call itself to do it.
any help with explanation so that i can understand the answer would be much appreciated!
this is the object im iterating on:
const customerData = {
createdAt: "2022-10-28T08:42:08.015Z",
email: "company#gmail.com",
firstName: "$$$$$$$",
fullName: "$$$$$$",
idNumber: "2813921321",
isEmailVerified: true,
isPhoneVerified: true,
lastName: "$$$$$",
metadata: {
birthDate: "2000-08-19 00:00:00.000",
gender: "Male",,
region: "",
status: "Adult",
statusExtra: "Student",
},
phone: "######",
project: "hlkjhkljhkjhk",
updatedAt: "2022-11-01T10:26:32.677Z",
username: null,
_id: "hlkjhlkjhlkjhlkjhlkjh",
};
see metadata? currently im outputting only the fields of the main(parent) object, but i also want to output the data which is contained in the 'metadata' key using recursion.
A solution to this could be to check if the key item[0] is "metadata". Then you could do the same as you did with the customerDraft object. Get the entries an map over them.
Note that I destructured the array you get from the .entries to make it more explicit what the variables are.
if (item[0] === "metadata") {
const inputs = Object.entries(item[1]).map(([metaKey, metaValue]) => (
<CStandardInput
key={metaKey}
allowClear
defaultValue={metaValue}
disableUnderline
formclasses={{ root: classes.root }}
htmlFor={`standard-customer-edit-${metaKey}`}
id={`standard-customer-edit-${metaKey}`}
label={metaKey}
onChange={(event) => {
setDraftCustomer({
...customerDraft,
fullName: event.target.value,
});
setDraftProfile({
...customerProfileDraft,
fullName: event.target.value,
});
}}
size="large"
/>
));
return <>{inputs}</>;
}
return (
<CStandardInput
...
EDIT:
To support the nested data with recursion, I've created a function with returns an input for every key, value pair in the data.
You can add your extra if statements as desired
const renderInputs = (data) => {
const inputs = Object.entries(data).map(([key, value]) => {
if (
typeof value === "object" &&
!Array.isArray(value) &&
value !== null
) {
return renderInputs(value);
}
return (
<CStandardInput
key={key}
allowClear
defaultValue={value}
disableUnderline
formclasses={{ root: classes.root }}
htmlFor={`standard-customer-edit-${key}`}
id={`standard-customer-edit-${key}`}
label={key}
onChange={(event) => {
setDraftCustomer({
...customerDraft,
fullName: event.target.value,
});
setDraftProfile({
...customerProfileDraft,
fullName: event.target.value,
});
}}
size="large"
/>
);
});
return inputs;
};
return <>{renderInputs(customerData)}</>;
Hope this helps you with your project!
I have a component which controls the state in Redux through a dropdown. For example, if you selected dropdown 1 and set the option to 'Phone call,' it will set it through redux state management. This works.
After this selection, though, I want to populate it in a timeline component—this is an MUI element. While it works, it appears that I need to add conditional JavaScript so that an array will be populated based on the selections. Currently there are 3 selections max.
For example, a functioning component:
export default function JobPostInterviewVerticalStepper() {
const [activeStep, setActiveStep] = React.useState(0);
const interviewStep1 = useSelector(state => state.jobsearch.selectedInterview1)
const interviewStep2 = useSelector(state => state.jobsearch.selectedInterview2)
const interviewStep3 = useSelector(state => state.jobsearch.selectedInterview3)
const handleNext = () => {
setActiveStep((prevActiveStep) => prevActiveStep + 1);
};
const handleBack = () => {
setActiveStep((prevActiveStep) => prevActiveStep - 1);
};
const handleReset = () => {
setActiveStep(0);
};
const steps = [
{
label: 'step1',
},
{
label: 'step2',
},
{
label: 'step3',
},
{
label: 'Offer',
},
];
return (
<Wrapper>
<Stepper activeStep={activeStep} orientation="vertical">
{steps.map((step, index) => (
<Step key={step.label}>
<StepLabel
optional={
index === 3 ? (
<Typography fontSize="12px" >Last step</Typography>
) : null
}
>
{step.label}
</StepLabel>
<StepContent>
<Box sx={{ mb: 1 }}>
<div>
<Button
variant="contained"
onClick={handleNext}
sx={{ mt: .5, mr: .5 }}
>
{index === steps.length - 1 ? 'Continue' : 'Continue'}
</Button>
<Button
disabled={index === 0}
onClick={handleBack}
sx={{ mt: 1, mr: 1 }}
>
Back
</Button>
</div>
</Box>
</StepContent>
</Step>
))}
</Stepper>
</Wrapper>
);
}
In the above, I'd like to populate steps with the useSelector so that, as the next dropdown is selected, interviewStep1 will have data and therefore populate step 1 correspondingly as indicated in the data file. What's the best way to access the data/potentially have conditional rendering?
This is my initial state:
const [inputList, setInputList] = useState([
{ answer: "Answer 1", isCorrect: false, points: 0 },
]);
I am adding input field dynamically and initially there will be one field. There is an Add button through which I can add more fields and remove button with that I can remove fields. But the problem I'm facing, after submitting the value I want to clear all the fields and there should be only one field as it was initially.Suppose if I add 4 fields and after submitting there should be only one. But when I'm trying to do that its not working but add, remove and even setting empty the state is working but setting only one field is not working.I'm mapping depending on inputList value but still after submitting I'm getting all the fields. If I have added 4 fields then after submitting also its 4. The state value is not being changed at all.
ADD more function:
const handleAddClick = () => {
let count = inputList.length;
let newfield = {
answer: `Answer ${count + 1}`,
isCorrect: false,
points: 0,
};
setInputList([...inputList, newfield]);
};
remove function:
const handleRemoveClick = (index) => {
let list = [...inputList];
let newList = list.filter((item, ind) => ind !== index);
setInputList(newList);
};
submit function: This should reset the state to initial state that is only one field should be there But not working
const Save = () => {
setInputList([{ answer: "Answer 1", isCorrect: false, points: 0 }])
}
I tried to do loop also for deleting every index but it only removes one no matter how many times the loop runs
Whole Component:
import DeleteIcon from "#mui/icons-material/Delete";
import React from "react";
import Checkbox from "#mui/material/Checkbox";
import Radio from "#mui/material/Radio";
import FormControl from "#mui/material/FormControl";
import IconButton from "#mui/material/IconButton";
import Switch from "#mui/material/Switch";
import TextField from "#mui/material/TextField";
import { useState } from "react";
import PlusSign from "../../../../Files/img/plus.png";
import Header from "../../../Header/Header";
import Topbar from "../../../Topbar/Topbar";
import styles from "../MultipleResponse/MultipleResponse.module.scss";
import QuestionEditor from "../../Editor/QuestionEditor/QuestionEditor";
import ReactTag from "../../TagInput/ReactTag";
import FormControlLabel from "#mui/material/FormControlLabel";
import { styled } from "#mui/material/styles";
import axios from "axios";
import "react-quill/dist/quill.snow.css";
import AnswerEditor from "../../Editor/AnswerEditor/AnswerEditor";
import SelectComp from "../../SelectComp/SelectComp";
import {
timeOptions,
categoryOptions,
difficultyLevelOptions,
} from "../../../../utils/constants";
import { RadioGroup } from "#mui/material";
import Snackbars from "../../../Assesment/MyAssesment/Snackbar";
import { useEffect } from "react";
export default function MultipleChoice() {
const baseURL = "http://localhost:5000/api/question";
const [distributePoints, setDistributePoints] = useState(false);
const [inputList, setInputList] = useState([
{ answer: "Answer 1", isCorrect: false, points: 0 },
]);
const [question, setQuestion] = useState("");
const [tags, setTag] = useState([]);
const [title, setTitle] = useState("");
const [time, setTime] = useState("");
const [difficultyLevel, setdifficultyLevel] = useState("");
const [category, setCategory] = useState("");
const [whatToLook, setwhatToLook] = useState("");
const [questionRelevent, setQuestionRelevent] = useState("");
const [shuffle, setShuffle] = useState(false);
const [value, setValue] = React.useState("");
const [loading, setLoading] = useState(false);
const [loading2, setLoading2] = useState(false);
const [snackText, setSnackText] = useState("");
const [severity,setSeverity]=useState("")
useEffect(() => {
const items = JSON.parse(localStorage.getItem("tempData"));
if (items) {
setTitle(items[0].title);
setdifficultyLevel(items[0].level);
setwhatToLook(items[0].whatToLookFor);
setQuestionRelevent(items[0].whyQuestionIsRelevant);
setTag(items[0].tags);
setTime(items[0].time);
setInputList(items[0].answers);
setCategory(items[0].category);
setQuestion(items[0].detail);
setShuffle(items[0].shuffleAnswers);
setDistributePoints(items[0].distributePoints);
setValue(items[0].radioValue);
}
}, []);
const [snack, setSnack] = React.useState(false);
const closeSnackbar = (event, reason) => {
if (reason === "clickaway") {
return;
}
setSnack(false);
};
const IOSSwitch = styled((props) => (
<Switch
focusVisibleClassName=".Mui-focusVisible"
disableRipple
{...props}
/>
))(({ theme }) => ({
width: 40,
height: 24,
padding: 0,
marginRight: 10,
"& .MuiSwitch-switchBase": {
padding: 0,
margin: 2,
transitionDuration: "300ms",
"&.Mui-checked": {
transform: "translateX(16px)",
color: "#fff",
"& + .MuiSwitch-track": {
backgroundColor:
theme.palette.mode === "dark" ? "#3699FF" : "#3699FF",
opacity: 1,
border: 0,
},
"&.Mui-disabled + .MuiSwitch-track": {
opacity: 0.5,
},
},
"&.Mui-focusVisible .MuiSwitch-thumb": {
color: "#33cf4d",
border: "6px solid #fff",
},
"&.Mui-disabled .MuiSwitch-thumb": {
color:
theme.palette.mode === "light"
? theme.palette.grey[100]
: theme.palette.grey[600],
},
"&.Mui-disabled + .MuiSwitch-track": {
opacity: theme.palette.mode === "light" ? 0.7 : 0.3,
},
},
"& .MuiSwitch-thumb": {
boxSizing: "border-box",
width: 20,
height: 20,
backgroundColor: "#FFFFFF",
},
"& .MuiSwitch-track": {
borderRadius: 26 / 2,
backgroundColor: theme.palette.mode === "light" ? "#E9E9EA" : "#39393D",
opacity: 1,
transition: theme.transitions.create(["background-color"], {
duration: 500,
}),
},
}));
const switchHandler = (event) => {
setDistributePoints(event.target.checked);
};
const handleInputChange = (e, index) => {
const list = [...inputList];
list[index]["answer"] = e;
setInputList(list);
};
const handlePointChange = (e, index) => {
const { name, value } = e.target;
const list = [...inputList];
list[index][name] = value;
setInputList(list);
};
const handleRemoveClick = (index) => {
let list = [...inputList];
let newList = list.filter((item, ind) => ind !== index);
setInputList(newList);
};
const handleAddClick = () => {
let count = inputList.length;
let newfield = {
answer: `Answer ${count + 1}`,
isCorrect: false,
points: 0,
};
setInputList([...inputList, newfield]);
};
const SaveAndAddAnother = () => {
setLoading2(true);
const newInputList = inputList.map(({ points, ...rest }) => {
return rest;
});
try {
axios
.post(baseURL, {
questionType: "MULTIPLE_CHOICE",
time: time.toString(),
title: title,
level: difficultyLevel,
detail: question,
category: category,
tags: tags,
whatToLookFor: whatToLook,
whyQuestionIsRelevant: questionRelevent,
answers: distributePoints ? inputList : newInputList,
distributePoints: distributePoints,
shuffleAnswers: shuffle,
})
.then((response) => {
setLoading(false);
if (response.data.result === 1) {
setSnack(true);
setSeverity('success')
setTimeout(() => {
setLoading2(false);
}, 1000);
setSnackText("Question added successfully");
setTime("");
setTitle("");
setdifficultyLevel("");
setQuestion("");
setCategory("");
setTag([]);
setwhatToLook("");
setQuestionRelevent("");
setDistributePoints(false);
setShuffle(false);
setValue("A");
let newList = [{ answer: "Answer 1", isCorrect: false, points: 0 }];
setInputList(newList);
localStorage.removeItem("tempData");
} else {
setLoading(false);
}
})
.catch((err) => {
// Handle error
setLoading2(false);
setSnack(true)
setSnackText("Error Adding question")
setSeverity('error')
});
} catch (e) {
setLoading(false);
}
};
const Save = () => {
setInputList([{ answer: "Answer 1", isCorrect: false, points: 0 }])
// setSnack(true);
// setSnackText("Question Saved as draft");
// setSeverity('success')
// setLoading(true);
// const localData = [
// {
// questionType: "MULTIPLE_CHOICE",
// time: time.toString(),
// title: title,
// level: difficultyLevel,
// detail: question,
// category: category,
// tags: tags,
// whatToLookFor: whatToLook,
// whyQuestionIsRelevant: questionRelevent,
// answers: inputList,
// distributePoints: distributePoints,
// shuffleAnswers: shuffle,
// radioValue: value,
// },
// ];
// localStorage.setItem("tempData", JSON.stringify(localData));
// setTimeout(() => {
// setLoading(false);
// }, 2000);
};
const questionText = (value) => {
setQuestion(value);
};
const selectedTags = (tags) => {
setTag(tags);
};
const handleTitle = (e) => {
setTitle(e.target.value);
};
const handleTime = (e) => {
setTime(e.target.value);
};
const handleDifficulty = (e) => {
setdifficultyLevel(e.target.value);
};
const handleCategory = (e) => {
setCategory(e.target.value);
};
const handleWhatToLookFor = (e) => {
setwhatToLook(e.target.value);
};
const handleQuestionRelevent = (e) => {
setQuestionRelevent(e.target.value);
};
const handleShuffle = (event) => {
setShuffle(event.target.checked);
};
function handleRadioButton(event, i) {
if (event.target.value === value) {
setValue("");
} else {
setValue(event.target.value);
}
const list = [...inputList];
Object.keys(list).forEach((key) => {
list[key]["isCorrect"] = false;
});
list[i]["isCorrect"] = true;
setInputList(list);
}
return (
<div className={styles.mainContainer}>
<Header />
<Topbar
questionType="Multiple-response"
loading={loading}
loading2={loading2}
SaveAndAddAnother={SaveAndAddAnother}
save={Save}
/>
<div className={styles.Container}>
<div className={styles.questionContainer}>
<div className={styles.left}>
<p className={styles.question}>Question</p>
<div className={styles.input}>
<TextField
variant="standard"
InputProps={{
disableUnderline: true,
}}
className={styles.title}
placeholder="Title.."
value={title}
onChange={(e) => handleTitle(e)}
/>
<QuestionEditor
question={question}
questionText={questionText}
theme="snow"
/>
</div>
<div className={styles.category}>
<ReactTag tags={tags} selectedTags={selectedTags} />
</div>
</div>
<div className={styles.right}>
<div className={styles.rightText}>
<span className={styles.heading}>Select the right answer</span>
<div>
<IOSSwitch
checked={distributePoints}
onChange={switchHandler}
/>
<span className={styles.instructinHeading}>
Distribute points across answers
</span>
</div>
</div>
<div className={styles.answers}>
<div className={styles.radio}>
<FormControl style={{ display: "flex", flex: 1 }}>
<RadioGroup
aria-labelledby="demo-controlled-radio-buttons-group"
name="controlled-radio-buttons-group"
value={value}
>
{inputList.map((x, i) => {
return (
<div key={i}>
<div key={i} className={styles.options}>
{!distributePoints ? (
<FormControlLabel
key={i}
value={x.answer}
control={
<Radio
onClick={(e) => handleRadioButton(e, i)}
/>
}
defaultChecked={false}
/>
) : (
""
)}
<div className="editor">
<AnswerEditor
handleAnswer={handleInputChange}
index={i}
theme="bubble"
val={x.answer}
/>
</div>
{distributePoints ? (
<div className={styles.inputCounter}>
<TextField
variant="standard"
name="points"
InputProps={{
disableUnderline: true,
type: "number",
}}
inputProps={{
min: 0,
style: { textAlign: "center" },
}}
value={x.points}
onChange={(e) => handlePointChange(e, i)}
/>
</div>
) : (
""
)}
{inputList.length > 1 && (
<IconButton
onClick={() => handleRemoveClick(i)}
className={styles.icon}
>
<DeleteIcon
sx={{ fontSize: 24, color: "#3699FF" }}
/>
</IconButton>
)}
</div>
{inputList.length - 1 === i && (
<div className={styles.bottomItemContainer}>
{distributePoints ? (
<div className={styles.pointsInfoText}>
Allocate points across answers. Give the best
answer 5 points
</div>
) : (
""
)}
{!inputList.some((el) => el.isCorrect === true) &&
!distributePoints ? (
<div className={styles.pointsInfoText}>
Select the correct answer
</div>
) : (
""
)}
<div
style={{
display: "flex",
justifyContent: "space-between",
}}
>
<button
onClick={handleAddClick}
type="button"
className={styles.addButton}
>
<img src={PlusSign} alt="" /> Add another
answer
</button>
<div className={styles.label}>
<Checkbox
checked={shuffle}
onChange={handleShuffle}
style={{ color: "#00A3FF" }}
/>
Shuffle answer
</div>
</div>
</div>
)}
</div>
);
})}
</RadioGroup>
</FormControl>
</div>
</div>
</div>
</div>
<div className={styles.itemContainer}>
<div className={styles.timeContainer}>
<SelectComp
handleChange={handleTime}
title="Time to answer the question"
val={time}
optionData={timeOptions}
/>
</div>
<div className={styles.timeContainer}>
<SelectComp
handleChange={handleCategory}
title="Question Category"
val={category}
optionData={categoryOptions}
/>
</div>
</div>
<div className={styles.itemContainer}>
<div style={{ flex: 1 }}>What to look for in the answer?</div>
<div style={{ flex: 1 }}>Why is this question relevant?</div>
</div>
<div className={styles.itemContainer}>
<div className={styles.releventText}>
<TextField
variant="standard"
InputProps={{
disableUnderline: true,
}}
className={styles.text}
placeholder="What to look for in the answer..."
value={whatToLook}
onChange={(e) => handleWhatToLookFor(e)}
/>
</div>
<div className={styles.releventText}>
<TextField
variant="standard"
InputProps={{
disableUnderline: true,
}}
className={styles.text}
placeholder="Why is this question relevant.."
value={questionRelevent}
onChange={(e) => handleQuestionRelevent(e)}
/>
</div>
</div>
<div className={styles.itemContainer}>
<div className={styles.difficultyLevel}>
<SelectComp
handleChange={handleDifficulty}
title="Difficulty Level"
val={difficultyLevel}
optionData={difficultyLevelOptions}
/>
</div>
{/* <div style={{ flex: 1 }}>Why is this question relevant?</div> */}
</div>
<div className={styles.bottomInfo}>
<div className={styles.element}>
<div className={styles.circle}></div>
<div className={styles.text}>
How should I formate my questions?
</div>
</div>
<div className={styles.element}>
<div className={styles.circle2}></div>
<div className={styles.text}>
{" "}
How do I use the question editor?
</div>
</div>
<div className={styles.element}>
<div className={styles.circle3}></div>
<div className={styles.text}>How do I use the formula editor?</div>
</div>
</div>
</div>
{snack && (
<Snackbars
text={snackText}
snack={snack}
closeSnackbar={closeSnackbar}
severity={severity}
/>
)}
{/* <div style={{ marginTop: 20 }}>{JSON.stringify(inputList)}</div> */}
</div>
);
}
Hello Im trying to design a step by step wizard form in React using TypeScript and JS but when I click on my next (continue) button I receive this error:
this.props.nextStep is not a function
My code has 2 different step counting systems, one for the steps of the UI stepper (progress bar) and one for swiping through/showing the forms and when I click on the button both of them need to happen. Here are my components in Sandbox: a tsx and a js example of 2 of the form pages, my main Form Builder that contains my buttons, The Stepper and the forms (CreateJob.js) and my UI stepper seperate component (Stepper.js) :https://codesandbox.io/s/tsx-rj694
what seems to be the problem?
Edit: the fuction that returns the error is (CreateJob.js)
continue
and the forms using props are in Sandbox
class CreateJob extends Component {
constructor () {
super()
this.state = {
currentStep: 1
}
this.Formstate = {
Formstep: 1
}
}
Formstate = {
//Formstep:1,
Title:'',
ActivationDate:'',
ExpirationDate:'',
DirectManager:'',
HRBP:'',
Details:'',
MinE:'',
WType:'',
Address:'',
Department:'',
Salary:''
}
nextStep =() => {
const {Formstep} = this.Formstate
this.setState({
Formstep: Formstep + 1
})
}
prevStep =() => {
const {Formstep} = this.Formstate
this.setState({
Formstep: Formstep - 1
})
}
handleClick = clickType => {
const {currentStep} = this.state
let newStep = currentStep
clickType === 'next' ? newStep++ : newStep--
if (newStep > 0 && newStep <= 6) {
this.setState({
currentStep: newStep
});
}
}
handleChange = input => e => {
this.setState({ [input]: e.target.value });
};
continue = e => {
e.preventDefault();
this.props.nextStep();
};
back = e => {
e.preventDefault();
this.props.prevStep();
};
render () {
const stepsArray = [
'ورود اطلاعات اولیه',
'توضیحات فرصت شغلی',
'نیازمندی ها',
'تایید اطلاعات',
'ثبت'
]
const { Formstep } = this.Formstate
const {currentStep} = this.state
const {Title,ActivationDate,ExpirationDate,DirectManager,HRBP,Detailss,MinE,WType,Address,Department,Salary } = this.Formstate;
const values1 ={Title,ActivationDate,ExpirationDate,DirectManager,HRBP}
const values2={Detailss}
const values3={MinE,WType,Address,Department,Salary}
const fullValues ={Title,ActivationDate,ExpirationDate,DirectManager,HRBP,Detailss,MinE,WType,Address,Department,Salary}
return (
<div>
<HRPanel />
<div>
<WidgetContainer>
<Widget padding style={{ fontFamily: 'IranSans',
textAlign: 'right',
fontSize: '14px',
height: '20px',
boxShadow: '1px 1px 1px 0px #888888',
backgroundColor: '#f3eaf7'}}>
<h3 style={{
position: 'relative',
bottom: '12px'
}}
>
اضافه کردن فرصت شغلی جدید
</h3>
</Widget>
</WidgetContainer>
</div>
<WidgetContainer>
<Stepper steps={stepsArray} currentStepNumber={currentStep - 1} />
</WidgetContainer>
<div>
{(()=>{
switch (Formstep) {
case 1:
return(
<MainInfo
nextStep={this.nextStep}
handleChange={this.handleChange}
values1={values1}
/>
)
case 2:
return(
<Details
nextStep={this.nextStep}
prevStep={this.prevStep}
handleChange={this.handleChange}
values2={values2}
/>
)
case 3:
return(
<AdditionalInfo
nextStep={this.nextStep}
prevStep={this.prevStep}
handleChange={this.handleChange}
values3={values3}
/>
)
case 4:
return(
<Confirmation
nextStep={this.nextStep}
prevStep={this.prevStep}
fullValues={fullValues}
/>
)
case 5:
return(
<Accepted/>
)
}
})()}
</div>
<div className='buttons-container'>
<button onClick={(e) => {this.handleClick(); this.back(e)}} className='previous'>قبلی</button>
<button form='my-form' type='submit' onClick={(e) =>{this.handleClick('next'); this.continue(e)}} className='next'>ادامه</button>
</div>
</div>
)
}
}
export default CreateJob
this.props.nextStep is not a function - because your props have not such function
try to use direct
this.nextStep();