React : Storing date to database via API Platform - reactjs

I'm using react in my symfony project. I need to save data through a form.
No problem for saving string data, but not date one.
Any idea of how to do it ?
const CvexperienceForm = React.memo(({post = null, onComment, cvexperience = null, onCancel = null}) => {
// Variables
const ref = useRef(null)
const refPost = useRef(null)
const refEntreprise = useRef(null)
const refDebut = useRef(null)
const onSuccess = useCallback(cvexperience => {
onComment(cvexperience)
refPost.current.value = ''
refEntreprise.current.value = ''
ref.current.value = ''
refDebut.current.value = ''
}, [ref, refEntreprise, refPost, refDebut, onComment])
// Hooks
const method = cvexperience ? 'PUT' : 'POST'
const url = cvexperience ? cvexperience['#id'] : '/api/cv_experiences'
const {load, loading, errors, clearError} = useFetch(url, method, onSuccess)
// Méthodes
const onSubmit = useCallback(e => {
e.preventDefault()
load({
entreprise: ref.current.value,
post: refPost.current.value,
description: refEntreprise.current.value,
debut: refDebut.current.value,
//fin: ref.current.value,
profile: "/api/cv_experiences/" + post
})
}, [load, ref, refEntreprise, refPost, refDebut, post])
// Affichage du form
const [show,setShow]=useState(false)
// Effets
useEffect(() => {
if (cvexperience && cvexperience.description && ref.current && refPost.current && refEntreprise.current && refDebut.current) {
ref.current.value = cvexperience.description
refPost.current.value = cvexperience.post
refEntreprise.current.value = cvexperience.entreprise
refDebut.current.value = cvexperience.debut
}
}, [cvexperience, ref, refPost, refEntreprise, refDebut])
return(
<div className="well">
{
show?
<form onSubmit={onSubmit}>
<button onClick={()=>setShow(false)} ><Icon icon="times"/> Replier</button>
{cvexperience === null && <fieldset>
</fieldset>}
<Field
name="description"
help="Veuillez renseigner tous les champs."
ref={ref}
refPost={refPost}
refEntreprise={refEntreprise}
refDebut={refDebut}
required
minLength={3}
namePost="post"
requiredPost="required"
childrenPost="Post"
nameEntreprise="entreprise"
requiredEntreprise="required"
childrenEntreprise="Entreprise"
nameDebut="debut"
requiredDebut="required"
childrenDebut="Date de début"
onChange={clearError.bind(this, 'description')}
error={errors['description']}
/>
<div className="form-group">
<button className="theme-btn btn-style-one small text-white" disabled={loading}>
{cvexperience === null ? 'Envoyer' : 'Editer'}
</button>
{onCancel && <button className="theme-btn btn-style-eight small" onClick={onCancel}>
Annuler
</button>}
</div>
</form>
:null
}
<button onClick={()=>setShow(true)} ><Icon icon="plus"/> Ajouter une expérience</button>
</div>
)
})

Related

React Guarantee that part of function runs after DOM updates

Currently I have a textarea like this:
<textarea
onChange={handleTextAreaChange}
ref={textAreaRef as MutableRefObject<HTMLTextAreaElement>}
id={id}
value={content}
></textarea>
I am implementing some buttons to add markdown to the textarea to make it easier for the user to update and have this function for bold:
const handleBoldClick = useCallback(() => {
const selectionStart = textAreaRef.current?.selectionStart;
const selectionEnd = textAreaRef.current?.selectionEnd;
if (selectionStart && selectionEnd) {
setContent(
prevContent =>
prevContent.substring(0, selectionStart) +
'**' +
prevContent.substring(selectionStart, selectionEnd) +
'**' +
prevContent.substring(selectionEnd, prevContent.length)
);
} else {
setContent(prevContent => prevContent + '****');
// Want this to run after textarea gets updated
textAreaRef.current?.focus();
textAreaRef.current?.setSelectionRange(
content.length - 3,
content.length - 3
);
}
const changeEvent = new Event('change', { bubbles: true });
// Want to run this after textarea is updated
textAreaRef.current?.dispatchEvent(changeEvent);
}, [content]);
setContent is the setter for content which is passed to the textarea. Is there a way to guarantee the parts I've marked with comments as wanting to only run once the DOM gets updated run when I want them to?
I finagled around with things and went with this approach (gonna post the entire component, which contains some stuff irrelevant to the question):
const MarkdownTextArea = ({
value,
onBlur = () => {},
onChange = () => {},
touched = false,
error,
id,
label
}: MarkdownTextAreaProps) => {
const [content, setContent] = useState(value ?? '');
const [numberOfRows, setNumberOfRows] = useState(5);
const [numberOfCols, setNumberOfCols] = useState(20);
const [isPreview, setIsPreview] = useState(false);
const [changed, setChanged] = useState<'bold' | null>();
const textAreaRef = useRef<HTMLTextAreaElement>();
useEffect(() => {
const setColsAndRows = () => {
const newColumnsNumber = Math.floor(
(textAreaRef.current?.offsetWidth ?? 100) /
(convertRemToPixels(1.2) / 1.85)
);
setNumberOfCols(newColumnsNumber);
setNumberOfRows(calculateNumberOfRows(content, newColumnsNumber));
};
setColsAndRows();
window.addEventListener('resize', setColsAndRows);
return () => {
window.removeEventListener('resize', setColsAndRows);
};
}, [content]);
const handleTextAreaChange: ChangeEventHandler<HTMLTextAreaElement> =
useCallback(
event => {
onChange(event);
setContent(event.target.value);
setNumberOfRows(
calculateNumberOfRows(
event.target.value,
textAreaRef.current?.cols ?? 20
)
);
},
[onChange]
);
const handleBoldClick = useCallback(() => {
const selectionStart = textAreaRef.current?.selectionStart;
const selectionEnd = textAreaRef.current?.selectionEnd;
if (selectionStart && selectionEnd) {
setContent(
prevContent =>
prevContent.substring(0, selectionStart) +
'**' +
prevContent.substring(selectionStart, selectionEnd) +
'**' +
prevContent.substring(selectionEnd, prevContent.length)
);
} else {
setContent(prevContent => prevContent + '****');
}
setChanged('bold');
}, []);
if (changed && textAreaRef.current?.value === content) {
const changeEvent = new Event('change', { bubbles: true });
textAreaRef.current?.dispatchEvent(changeEvent);
if (changed === 'bold' && textAreaRef.current) {
textAreaRef.current.focus();
textAreaRef.current.selectionStart = content.length - 2;
textAreaRef.current.selectionEnd = content.length - 2;
}
setChanged(null);
}
return (
<div className={classes.container} data-testid="markdown-text-area">
<div className={classes['header']}>
<label className={classes.label} htmlFor={id}>
{label}
</label>
<Button
positive
style={{ justifySelf: 'flex-end' }}
onClick={() => setIsPreview(prev => !prev)}
>
{isPreview ? 'Edit' : 'Preview'}
</Button>
</div>
<div className={classes['text-effect-buttons']}>
<button
className={classes['text-effect-button']}
onClick={handleBoldClick}
type="button"
style={{ fontWeight: 'bold' }}
>
B
</button>
</div>
{isPreview ? (
<div className={classes['markdown-container']} id={id}>
<MarkdownParser input={content} />
</div>
) : (
<textarea
onChange={handleTextAreaChange}
className={`${classes['text-input']}${
error && touched ? ` ${classes.error}` : ''
}`}
ref={textAreaRef as MutableRefObject<HTMLTextAreaElement>}
rows={numberOfRows}
cols={numberOfCols}
onBlur={onBlur}
id={id}
value={content}
></textarea>
)}
{error && touched && (
<div className={classes['error-message']}>{error}</div>
)}
</div>
);
};
The part of the following component most relevant to answering the question is the following:
if (changed && textAreaRef.current?.value === content) {
const changeEvent = new Event('change', { bubbles: true });
textAreaRef.current?.dispatchEvent(changeEvent);
if (changed === 'bold' && textAreaRef.current) {
textAreaRef.current.focus();
textAreaRef.current.selectionStart = content.length - 2;
textAreaRef.current.selectionEnd = content.length - 2;
}
setChanged(null);
}

how to update input defaultvalue using useRef (after the initial render?)

const Component = ()=>{
const [list, setList] = useState(getLocalStorage());
const [isEditing, setIsEditing] = useState(false);
const [itemToEdit, setItemToEdit] = useState();
const refContainer = useRef(null);
const putLocalStorage = () => {
localStorage.setItem("list", JSON.stringify(list));
};
const editItem = (id) => {
refContainer.current.focus();
setItemToEdit(() => {
return list.find((item) => item.id === id);
});
setIsEditing(true);
};
const handleSubmit = (e)=>{
e.preventDefault();
let nameValue = refContainer.current.value;
if (isEditing){
setList(list.map((item)=>{
if (item.id === itemToEdit.id){
return {...item, name: nameValue};
}
else {
return item;
}
);
}
else {
let newItem = {
id: new Date().getItem().toString(),
name: nameValue,
}
setList([...list, newItem])
}
nameValue="";
setIsEditing(false);
}
useEffect(() => {
putLocalStorage();
}, [list]);
return (
<div>
<form onSubmit={handleSubmit}>
<input type="text" ref={refContainer} defaultValue={isEditing ? itemToEdit.name : ""}/>
<button type="submit">submit</button>
</form>
<div>
{list.map((item) => {
const { id, name } = item;
return (
<div>
<h2>{name}</h2>
<button onClick={() => editItem(id)}>edit</button>
<button onClick={() => deleteItem(id)}>
delete
</button>
</div>
);
})}
</div>
</div>
)
}
So this part:
<input type="text" ref={refContainer} defaultValue={isEditing ? itemToEdit.name : ""} />
I want to show to users what they are editing by displaying the itemToEdit on the input.
It works on the first time when the user clicks edit button
But after that, the defaultValue does not change to itemToEdit
Do you guys have any idea for the solution?
(i could use controlled input instead, but i want to try it with useRef only)
Otherwise, placeholder will be the only solution...
The defaultValue property only works for inicial rendering, that is the reason that your desired behavior works one time and then stops. See a similar question here: React input defaultValue doesn't update with state
One possible solution still using refs is to set the itemToEdit name directly into the input value using ref.current.value.
const editItem = (id) => {
refContainer.current.focus();
setItemToEdit(() => {
const item = list.find((item) => item.id === id);
refContainer.current.value = item.name;
return item;
});
setIsEditing(true);
};

ReactJS form submission using UseState

So I've Managed to made an html form for my react app. The problem is that I cannot output on my console log the inputs on my form. it just shows me this:
Console Log Output Pic
Here is my code snippet:
import { useState } from "react";
const InputForm = (props) => {
const [inputData, setInputData] = useState(
{
enteredFName: "",
enteredLName: "",
enteredEmail: "",
enteredEid: "",
enteredBirthday: "",
},
[]
);
//First Name Validation
const [enteredFName, setEnteredFName] = useState("");
const [enteredFNameTouch, setEnteredFNameTouch] = useState(false);
const enteredFNameFilled = enteredFName.trim() !== "";
const enteredFNameNotFilled = !enteredFNameFilled && enteredFNameTouch;
const enteredFNameValid = /^[A-Za-z\s]+$/.test(enteredFName);
const enteredFNameInvalid = !enteredFNameValid && enteredFNameNotFilled;
const fNameChangeHandler = (event) => {
setEnteredFName(event.target.value);
};
const fNameBlurHandler = () => {
setEnteredFNameTouch(true);
};
//Last Name Validation
const [enteredLName, setEnteredLName] = useState("");
const [enteredLNameTouch, setEnteredLNameTouch] = useState(false);
const enteredLNameFilled = enteredLName.trim() !== "";
const enteredLNameNotFilled = !enteredFNameFilled && enteredLNameTouch;
const enteredLNameValid = /^[A-Za-z\s]+$/.test(enteredLName);
const enteredLNameInvalid = !enteredLNameValid && enteredLNameNotFilled;
const lNameChangeHandler = (event) => {
setEnteredLName(event.target.value);
};
const lNameBlurHandler = () => {
setEnteredLNameTouch(true);
};
//EmailValidation
const [enteredEmail, setEnteredEmail] = useState("");
const [enteredEmailTouch, setEnteredEmailTouch] = useState(false);
const enteredEmailFilled = enteredEmail.trim() !== "";
const enteredEmailNotFilled = !enteredEmailFilled && enteredEmailTouch;
const enteredEmailValid = enteredEmail.includes("#");
const enteredEmailInvalid = !enteredEmailValid && enteredEmailNotFilled;
const emailChangeHandler = (event) => {
setEnteredEmail(event.target.value);
};
const emailBlurHandler = () => {
setEnteredEmailTouch(true);
};
//EIDValidation
const [enteredEid, setEnteredEid] = useState("");
const [enteredEidTouch, setEnteredEidTouch] = useState(false);
const enteredEidFilled = enteredEid.trim() !== "";
const enteredEidNotFilled = !enteredEidFilled && enteredEidTouch;
const eidChangeHandler = (event) => {
setEnteredEid(event.target.value);
};
const eidBlurHandler = () => {
setEnteredEidTouch(true);
};
//Birthday Validation
const [enteredBirthday, setEnteredBirthday] = useState("");
const [enteredBirthdayTouch, setEnteredBirthdayTouch] = useState(false);
const enteredBirthdayFilled = enteredBirthday.trim() !== "";
const enteredBirthdayNotFilled =
!enteredBirthdayFilled && enteredBirthdayTouch;
const birthdayChangeHandler = (event) => {
setEnteredBirthday(event.target.value);
};
const birthdayBlurHandler = () => {
setEnteredBirthdayTouch(true);
};
let formValid = false;
if (
enteredFNameFilled &&
enteredLNameFilled &&
enteredEmailFilled &&
enteredEidFilled &&
enteredBirthdayFilled
) {
formValid = true;
}
const formSubmitHandler = (event) => {
event.preventDefault();
if (!enteredFNameValid || !enteredLNameValid) {
alert("First Name and Last Name accepts letters only.");
return;
}
console.log(inputData);
setEnteredFName("");
setEnteredFNameTouch(false);
setEnteredLName("");
setEnteredLNameTouch(false);
setEnteredEmail("");
setEnteredEmailTouch(false);
setEnteredEid("");
setEnteredEidTouch(false);
setEnteredBirthday("");
setEnteredBirthdayTouch(false);
};
return (
<form onSubmit={formSubmitHandler}>
<div>
<h1>Please Enter your details below</h1>
</div>
<div className="control-group">
<div>
<label>First Name</label>
<input
type="text"
required
id="fName"
onChange={fNameChangeHandler}
onBlur={fNameBlurHandler}
value={enteredFName}
/>
{enteredFNameInvalid && <p>First Name is required.</p>}
</div>
<div>
<label>Last Name</label>
<input
type="text"
required
id="lName"
onChange={lNameChangeHandler}
onBlur={lNameBlurHandler}
value={enteredLName}
/>
{enteredLNameInvalid && <p>Last Name is required.</p>}
</div>
</div>
<div>
<label>Email</label>
<input
type="email"
required
id="email"
onChange={emailChangeHandler}
onBlur={emailBlurHandler}
value={enteredEmail}
/>
{enteredEmailInvalid && <p>Email is required.</p>}
</div>
<div>
<label>EID</label>
<input
type="number"
required
min="1"
step="1"
id="eid"
onChange={eidChangeHandler}
onBlur={eidBlurHandler}
value={enteredEid}
/>
{enteredEidNotFilled && <p>EID is required.</p>}
</div>
<div>
<label>Birthday</label>
<input
type="date"
required
id="birthday"
onChange={birthdayChangeHandler}
onBlur={birthdayBlurHandler}
value={enteredBirthday}
/>
{enteredBirthdayNotFilled && <p>Birthday is required.</p>}
</div>
<div>
<button type="submit" disabled={!formValid}>
Submit
</button>
</div>
</form>
);
};
export default InputForm;
How do I utilize the setInputData for my overall form submission? because I don't know where to put it on the code.
I don't think you need the inputData state, since all your values are already saved in other state variables.
Can you try to console log the following in your formSubmitHandler:
console.log({
enteredFName,
enteredLName,
enteredEmail,
enteredEid,
enteredBirthday,
})
If you insist on keeping the separate state object for submitted values, you can just replace the console.log by setInputData in formSubmitHandler:
setInputData({
enteredFName,
enteredLName,
enteredEmail,
enteredEid,
enteredBirthday,
}
First of all , If you have multi form in your react project, try to use your own hook or use react form hook or formik to make it easier and lower code , its better to get object from Form and pass it like this :
setInputData(data)
If is not possible , set each property in setInputData, and if you have a lot of state , try to write dynamic handler function instead of function for each item , like this :
function handle(type,value){
useState(prev => {
...prev,
[type]:value
})
}

react component rendering after state change working in delay

I have those 2 components
FormComponent
const FormComponent = (props) => {
//keep the current step in the component state
const [currentStep, setCurrentStep] = useState(props.step);
const [componentData, setComponentData] = useState(null);
const [componentQuestions, setQuestions] = useState(null);
const [errors, setErrors] = useState();
const updateUserResponse = props.updateUserResponse;
const renderStepsTracker = props.renderStepsTracker;
/**
* Get the component content after mount
*/
useEffect(() => {
if (componentData) {
setQustionsData(componentData);
}
//fire if componentData has changed or error object is changed
}, [errors,componentData]);
/**
* Get the component content after mount
*/
useEffect(() => {
if (currentStep && !componentData) {
let stepData = currentStep.stepdata;
stepData.update = getUpdateTime();
setComponentData(stepData);
}
});
/**
* Get the current time in milliseconds
*/
const getUpdateTime = () => {
var d = new Date();
var n = d.getTime();
return n
}
/**
* Extract the questions from the step object and
* set the state accordingly
*/
const setQustionsData = (stepData) => {
let questions = stepData.questions;
//assign a react component to each question
questions.map((question, key) => {
const component = getComponent(question);
const question_id = question.question_id;
questions[key].component = component;
questions[key].error = (null != errors && 'undefined' !== typeof errors[question_id]) ? errors[question_id] : '';
})
setQuestions(questions);
}
/**
* a callback function to validate the current form
* #param {object} step
*/
const validateForm = (step) => {
const formErrors = props.validateForm(step);
setErrors(formErrors)
}
/**
* Render the form
*/
const renderForm = () => {
return null !== componentQuestions ? (
<>
<div className="row full-height">
<div className="column">
<div className="form-wrapper">
<div className="questions">
{componentQuestions.map(question => {
return (
<>
{question.component}
</>
)
})}
</div>
<NextStepButton
step={currentStep}
nextStepCallback={props.jumpToStep}
validateForm={validateForm}
/>
</div>
</div>
</div>
</>
) : ''
}
/**
* Render the image in case it exists
*/
const renderImage = () => {
return (
<div className="full-height">
<img className="full-height-image" src={componentData.image} alt="" />
</div>
)
}
/**
* Get the question component
*/
const getComponent = (question) => {
switch (question.question_type) {
case "text":
return (
<div className="field-wrap">
<label>
<span className="question-label">{question.question_label}</span>
<span className="question-title">{question.question_title}</span>
<input type="text"
name={question.question_id}
onChange={updateUserResponse}
required={question.required}
/>
{question.error ?
<span className={"error-message " + question.error.reason}>{question.error_message}</span>
: ''}
</label>
</div>
)
}
}
return (
componentData ?
<>
<div className="row full-height expanded">
{componentData.image ?
<div className="column full-height col-collapse image-wrap">
{renderImage()}
</div>
: ''}
<div className="column full-height questions-container">
{renderStepsTracker()}
{renderForm()}
</div>
</div>
</>
: ''
)} enter code hereexport default FormComponent
**
NextStepButton
**
const NextStepButton = (props) => {
const step = props.step
const validateForm = props.validateForm
return props.step.nextButtonText ? (
<span className="next-step-button" onClick={() => {
validateForm( step )
}}>
{step.nextButtonText}
</span>
) : ''
}
export default NextStepButton
When the user clicks on the next step the form is validated and returns an array of errors,
The array of errors is set to the component state and then supposed to display the error
For some reason the error appears only after the user clicks 3 times on the next step button
Why is that ?
EDIT
this is the main component that contains the global form validation
const MainContainer = () => {
//set state variables
const [nextButtonText, setNextButtonText] = useState()
const [previousButtonText, setPreviousButtonText] = useState()
const [backButtonCls, setBackButtonCls] = useState()
const [currentStep, setCurrentStep] = useState()
const [stepsData, setStepsData] = useState()
const [currentStepNum, setCurrentStepNum] = useState(null)
const [classNames, setclassNames] = useState(null)
const [userData, setUserData] = useState({})
//set refrence for callbacks that use this component state
const ref = useRef({
stepsData: stepsData,
currentStep: currentStep
})
/**
* On Initial state
*/
useEffect(() => {
if (!stepsData) {
getStepsData().then((response) => {
initProcess(response)
})
}
})
/**
* Init the process after getting data from the server
*/
const initProcess = (response) => {
const steps = setStepsComponents(response.data.steps)
const initialStep = steps[0];
ref.current.stepsData = steps
setStepsData(steps)
setStep(initialStep, 1)
}
/**
* Form submission callback
* Loop over step questions and check for the appropriate userdata
*/
const validateForm = (submittedStep) => {
let errors = {};
if ('null' !== typeof submittedStep.stepdata.questions) {
const questions = submittedStep.stepdata.questions
questions.map((question, key) => {
const question_name = question.question_id
//if there is an error add it to the questions collection
if (question.required && !userData[question_name]) {
errors[question.question_id] = {
reason : 'required'
}
}
})
}
if (errors) {
return errors
}
return true;
}
/**
* Capture user settings on change
*/
const updateUserResponse = (event) => {
userData[event.target.name] = event.target.value;
setUserData(userData)
}
/**
* Set the appropriate step component
* #param {OBJECT} steps
*/
const setStepsComponents = (steps) => {
steps.map((step, key) => {
switch (steps[key]['component']) {
case "onboarding":
steps[key]['component'] = <Onboarding step={step} setUserData />
break;
case "questions":
steps[key]['component'] = <FormComponent
step={step}
renderStepsTracker={renderStepsTracker}
updateUserResponse={updateUserResponse}
validateForm={validateForm}
/>
break;
}
})
return steps
}
/**
* Callback function that creates the steps tracker on each component
*/
const renderStepsTracker = () => {
return ref.current.stepsData ? (
<>
<div className="stepsTracker">
<div className="row">
<div className="column">
<div className="stepsTrackerWrap">
{ref.current.stepsData.map((step, key) => {
const activeClass = ref.current.currentStep === key ? "active" : ""
return key > 0 ? (
<>
<div className={"stepTracker " + activeClass}>
<div className="stepTrackerImage">
<img src={step.icon} alt="" />
</div>
<label>{step.stepdata.title}</label>
</div>
<div className="stepSpacer"></div>
</>
) : ''
})}
</div>
</div>
</div>
</div>
</>
) : ''
}
/**
* Render the steps component if data is available
*/
const renderSteps = () => {
return stepsData ? (
<StepZilla
steps={stepsData}
onStepChange={onstepChange}
nextButtonText={nextButtonText}
showSteps={false}
backButtonText={previousButtonText}
backButtonCls={backButtonCls}
/>
) : ''
}
/**
* Set the data for the current step
* #param {object} step
*/
const setStep = (step, stepNum) => {
setNextButtonText(step.showNextButton ? step.nextButtonText : true)
setPreviousButtonText(step.previousButtonText)
setBackButtonCls(step.backButtonCls)
setCurrentStep(step)
setCurrentStepNum(stepNum)
ref.current.currentStep = stepNum
//add bottom padding in case the next button appears
if (step.showNextButton) {
setclassNames('padding')
} else {
setclassNames(' ')
}
}
/**
* Perform actions when step is changed
* #param {step} step
*/
const onstepChange = (stepNum) => {
if ('undefined' === typeof stepNum) {
stepNum = '0';
}
setCurrentStepNum(stepNum)
const nextStep = stepsData[stepNum];
setStep(nextStep, stepNum)
}
return (
<>
<div className={'step-progress ' + classNames}>
{renderSteps()}
</div>
</>
)
}
export default MainContainer

React Hook to display all boxes checked on a form

I've created a form and am saving the data to a json file locally. I can save all the data except for the questions with multiple selections and multiple checkboxes. It only saves the last one selected. I am trying to write a switch statement within a React Hook that is working to help save the submitted form. I keep getting an error "cannot identify type of undefined." I'm new to react and don't know what to do from here.
This is in my hooks folder:
export const useInputChange = (customValue, callback) => {
const [value, setValue] = useState(customValue ? customValue : "" || []);
const handleChange = (event) => {
var newValue;
switch (customValue.type) {
case "multipleSelection":
newValue = $("multipleSelection").find("option:checked");
break;
case "checkboxChoice":
newValue = $("checkboxChoice").find("input:checked");
break;
default:
newValue = event.target.value;
}
setValue(newValue);
if (callback) {
callback(event.target.name, newValue);
}
};
return {
value: value,
handleChange: handleChange
};
};
This is my callback in my components folder:
const callback = (name, value) => {
console.log("callback", name, value);
inlineData[name] = value;
setInlineData(inlineData);
console.log(inlineData);
};
The jquery works in the console to pull up the correct arrays.
This is the component:
export const Survey = (props) => {
const [page, setPage] = useState(1);
const [isFinalPage, setIsFinalPage] = useState(false);
const [surveyValues, setSurveyValues] = useState({});
const [loadedInputs, setLoadedInputs] = useState({});
const [question, setQuestion] = useState({});
const [inlineData, setInlineData] = useState({});
const { surveyId } = props;
const triggerBackendUpdate = () => {
console.log(question);
console.log(surveyValues);
setPage(1);
setSurveyValues({});
setQuestion({});
};
useEffect(() => {
if (surveyId) {
const inputDataFile = import(`./data_${surveyId}.json`);
inputDataFile.then((response) => {
setLoadedInputs(response.default);
});
}
});
const handleSubmit = (event) => {
event.preventDefault();
event.persist();
for (let formInput of event.target.elements) {
const isText = isTextInput(formInput.type);
console.log(formInput);
if (isText) {
surveyValues[formInput.name] = formInput.value;
question[formInput.question] = formInput.question;
}
if (formInput.type === "selectMultiple") {
let selected = [].filter.call(
formInput.options,
(option) => option.selected
);
console.log(formInput);
console.log(selected);
console.log(formInput.options.selected);
const values = selected.map((option) => option.value);
surveyValues[formInput.name] = values;
question[formInput.name] = formInput.question;
}
if (formInput.type === "checkbox") {
surveyValues[formInput.name] = formInput.value;
question[formInput.name] = formInput.question;
}
}
setQuestion(question);
setSurveyValues(surveyValues);
const nextPage = page + 1;
const inputs = props.inputs
? props.inputs.filter((inputOption) => inputOption.page ===
nextPage): [];
if (isFinalPage) {
triggerBackendUpdate();
} else {
if (inputs.length === 0) {
setIsFinalPage(true);
} else {
setPage(nextPage);
}
}
};
const callback = (name, value) => {
console.log("callback", name, value);
inlineData[name] = value;
setInlineData(inlineData);
console.log(inlineData);
};
const saveSurvey = async () => {
await fetch("/api/survey", {
method: "POST",
body: JSON.stringify(inlineData),
headers: {
"Content-Type": "application/json",
},
}).catch((error) => {
console.error(error);
});
};
const inputs = props.inputs
? props.inputs.filter((inputOption) => inputOption.page === page)
: [];
return (
<form onSubmit={handleSubmit}>
{isFinalPage !== true &&
inputs.map((obj, index) => {
let inputKey = `input-${index}-${page}`;
return obj.type === "radio" || obj.type === "checkbox" ? (
<SurveyRadioInput
object={obj}
type={obj.type}
required={props.required}
triggerCallback={callback}
question={obj.question}
defaultValue={obj.defaultValue}
name={obj.name}
key={inputKey}
/>
) : obj.type === "checkbox" ? (
<SurveyCheckboxInput
object={obj}
type={obj.type}
required={props.required}
triggerCallback={callback}
question={obj.question}
defaultValue={obj.defaultValue}
name={obj.name}
key={inputKey}
/>
) : obj.type === "select" ? (
<SurveySelectInput
className="form-control mb-3 mt-3"
object={obj}
type={obj.type}
question={obj.question}
required={props.required}
triggerCallback={callback}
defaultValue={obj.defaultValue}
name={obj.name}
key={inputKey}
/>
) : obj.type === "selectMultiple" ? (
<SurveySelectMultipleInput
className="form-control mb-3 mt-3"
object={obj}
type={obj.type}
question={obj.question}
required={props.required}
triggerCallback={callback}
defaultValue={obj.defaultValue}
name={obj.name}
key={inputKey}
/>
) : (
<SurveyTextInput
className="mb-3 mt-3 form-control"
object={obj}
type={obj.type}
question={props.question}
required={props.required}
triggerCallback={callback}
placeholder={obj.placeholder}
defaultValue={obj.defaultValue}
name={obj.name}
key={inputKey}
/>
);
})}
{isFinalPage !== true ? (
<button name="continue-btn" className="btn btn-primary my-5 mx-5">
Continue
</button>
) : (
<Link to="/thankyou">
<button
onClick={saveSurvey}
type="button"
className="btn btn-primary my-5 mx-5"
>
Submit Survey
</button>
</Link>
)}
</form>
);
};
This is in my inputs folder:
export const SurveySelectMultipleInput = (props) => {
const { object } = props;
const { value, handleChange } = useInputChange(
props.defaultValue,
props.triggerCallback
);
const inputType = isTextInput(props.type) ? props.type : "";
const inputProps = {
className: props.className ? props.className : "form-control",
onChange: handleChange,
value: value,
required: props.required,
question: props.question,
type: inputType,
name: props.name ? props.name : `${inputType}_${props.key}`,
};
console.log(value);
return (
<>
<div id={object.name}>
<h5>{props.question}</h5>
<select
{...inputProps}
name={object.name}
className={props.className}
multiple={object.multiple}
>
<option hidden value>
Select one
</option>
{object.options.map((data, index) => {
return (
<option
value={data.value}
id={`${object.name}-${index}`}
key={`${object.type}-${index}`}
className={`form-check ${props.optionClassName}`}
>
{data.label}
</option>
);
})}
</select>
</div>
</>
);
};
It's hard to tell exactly how your components and hooks behave without having an example showing their behavior and properties. Regardless, I made some assumptions and tried to answer:
First of all, what is the expected type of customValue in useInputChange? Are you expecting a string or an array? Then what is the type attribute on it that you're checking in your switch statement?
As for the jquery selector, what is multipleSelection? Is it the class name you're using for your select elements? Then your selector must start with a dot a/nd then you can get the value by calling .val method on the selected element:
newValue = $(".multipleSelection").val();
Here's a working example for multiple select elements, using your code: https://codepen.io/kaveh/pen/QWNNQMV
Note that I had to assign an arbitrary type attribute to VALUE to get it working with your switch statement.
All that being said, as I mentioned in my comment, it's recommended to use ref to access elements created by React and not other query selectors such as those you get from jquery.
https://reactjs.org/docs/refs-and-the-dom.html
https://reactjs.org/docs/hooks-reference.html#useref

Resources