Ok so I have been trying this for a moment and I don't know how to go about it, what I want is simple, generate a brand new form but with a react transtion group effect from the old form to the new form (everytime a user clicks the submit button)
you are a lifesaver if you can provide help or a thought.
this.state.transitionTimes I tried to increment a state key maybe watch that but it doesn't work
<div disabled={currentAccommodation.isLoading || currentRoom.isLoading} className="accommodation_popup_innerContainer_inputContainer_transition_container">
<SwitchTransition>
<CSSTransition
key={this.state.isAddRoom}
addEndListener={(node, done) =>
node.addEventListener('transitionend', done, false)
}
classNames="fade"
>
{(isAddRoom || this.state.isAddRoom) === true ? (
<CreateRoomForm
ScrollBar={ScrollBar}
LabelInput={LabelInput}
currentState={this.state}
handleChange={() => this.handleChange}
removeItem={this.removeItem}
handleMultipleChange={this.handleMultipleChange}
setTheState={this.setTheState}
error={this.state.error}
/>
) : ( // I want the transition on this side
<cssTransition
key={this.state.transitionTimes}
addEndListener={(node, done) =>
node.addEventListener('transitionend', done, false)
}
classNames="fade">
<CreateAccommodationForm
ScrollBar={ScrollBar}
LabelInput={LabelInput}
currentState={this.state}
handleChange={() => this.handleChange}
removeItem={this.removeItem}
handleMultipleChange={this.handleMultipleChange}
setTheState={this.setTheState}
OnChangeDescription={this.OnChangeDescription}
error={this.state.error}
/>
</cssTransition>
)}
</CSSTransition>
</SwitchTransition>
</div>
submit buttons
<Button
type="submit"
className="btn accommodation_popup_innerContainer_buttons_button"
value={isAddRoom ? 'Add' : 'Submit'}
onClick={
(isAddRoom || this.state.isAddRoom)
? this.handleAddRoomBtn // for the top form
: this.handleSubmitBtn // for th bottom form
}
/>
My submit button
handleAddRoomBtn = async (e) => {
e.preventDefault();
await this.setState({
...this.initiaState,
transitionTimes: this.state.transitionTimes + 1
})
console.log(this.state)
};
Related
Given a small quiz app, I'm trying to toggle a message under the questions to show the user that they have selected the correct answer or incorrect answer.
I have a small message that I'm gating with a boolean value which is set by the selection of the radio button (though the correct radio button isn't getting selected on initial click for some reason, only incorrect answers are), and need it to show a message of "You got it right" if the answer is true or "incorrect" if false. I can show true, but the logic isn't working to show false if the answer is incorrect and then clear the messaging when the "next question" button is clicked.
function App() {
let [points, setPoints] = useState(null);
let [counter, setCounter] = useState(null);
let [question, setQuestions] = useState();
let [isCorrect, setIsCorrect] = useState(false); <==Store correct answer selected
function Answer(props) {
return (
<li aria-labelledby="answers-list">
<label>
<input
type="radio"
name="answer_group"
className="answer"
value={props.answer}
onChange={checkAnswer}
/>
{props.answer}
</label>
</li>
);
}
function checkAnswer(e) {
let val = e.target.value;
let ans = question[counter].answers.filter((ans) => ans.value === val)[0];
displayCorrect(ans.correct);
//Toggles message to true if answer is correct
ans.correct === true ? setIsCorrect(true) : setIsCorrect(false);
}
function Quiz(props) {
return (
<div className="quiz">
<div className="quesiton" role="h2">
{props.question}
</div>
<ul className="answers">{props.children}</ul>
</div>
);
}
function displayCorrect(correct) {
let correct_msg = correct ? "correct" : "incorrect";
console.log("Answer was " + correct_msg);
}
function nextQuestion() {
setIsCorrect(false); <== Should hide message when user selects "next question" button
if (document.querySelector('input[name="answer_group"]:checked') == null) {
alert("Must select an answer before proceeding to the next question");
return;
}
let val = document.querySelector('input[name="answer_group"]:checked')
.value;
let answerObj = question[counter].answers.filter(
(ans) => ans.value === val
)[0];
let updated_points = answerObj.correct ? points + 1 : points;
setPoints(updated_points);
let nextQuestion = counter + 1;
if (counter < question.length - 1) {
setCounter(nextQuestion);
} else {
setCounter(0);
}
displayCorrect(answerObj.correct);
}
return (
<div className="Quiz slide-top">
{!is_started ? (
<div className="start-intro-wrapper">
<h1 className="name">Quiz App</h1>
<Starter start={start} />
</div>
) : (
<div className="quick-wrapper slide-in-bottom">
<Quiz question={question[counter].question}>
{question[counter].answers.map((answer, index, arr) => {
return (
<Answer
key={index}
index={index}
answer={answer.value}
correct={answer.correct}
/>
);
})}
</Quiz>
<div className="answer-controls">
{isCorrect === true ? <p>You got it right!</p> : null} <== Messaging should show if user has selected correct message if correct option is selected
<button onClick={(e) => nextQuestion()}>
Next question
</button>
</div>
</div>
)}
</div>
);
}
Codesandbox Link
It doesn't display anything because you put null in the other condition for the display.
If you replace it with {isCorrect === true ? <p>You got it right!</p> : <p>You got it wrong!</p>} the correct message is displayed.
You can also remove completly the function displayCorrect it's only writing in the console.
If you want to clear the message when the user click on "next question" you could do it with a new variable hasAnswered set at false by default.
You set it to true in your checkAnswer function, and to false in your nextQuestion function.
The code for the text at the bottom would then looks like:
{hasAnswered &&
(isCorrect === true ? <p>You got it right!</p> : <p>You got it wrong!</p>)
}
Regarding the issue with you needing to click twice to update the radio button selected, i think it's a re-render issue when isCorrect change.
To avoid the issue, you could store the value checked by the user and use this to display if the radio should be checked or no.
It works with the following changes
let [isChecked, setChecked] = useState(null);
[...]
function Answer(props) {
return (
<li aria-labelledby="answers-list">
<label>
<input
checked={isChecked===props.answer}
type="radio"
name="answer_group"
className="answer"
value={props.answer}
onChange={checkAnswer}
/>
{props.answer}
</label>
</li>
);
}
function checkAnswer(e) {
let val = e.target.value;
let ans = question[counter].answers.filter((ans) => ans.value === val)[0];
setIsCorrect(ans.correct);
setChecked(ans.value);
}
You also need setChecked(null) in your nextQuestion function to reset everything.
I am working on a project as a means to practice some stuff in react and I need to render a button for each of the map data. I did this successfully but expand and collapse has been giving me issue. Whenever I click on the button all data collapse and expand together.
const DataFetch = () => {
...
const [btnValue, setBtnValue] = useState('+');
const handleChange = (e) => {
setShowData(!showData);
setBtnValue(btnValue === '+' ? '-' : '+');
};
return (
<div className='container'>
...
{studentResults
.filter((val) => {
if (searchTerm === '') {
return val;
} else if (
val.firstName.toLowerCase().includes(searchTerm.toLowerCase()) ||
val.lastName.toLowerCase().includes(searchTerm.toLowerCase())
) {
return val;
} else {
return null;
}
})
.map((student) => {
return (
<div key={student.id}>
<div className='card'>
<div className='row'>
<div className='col-2'>
<div className='pic'>
<img src={student.pic} alt='avatar' />
</div>
</div>
<div className='col'>
<div className='details'>
<p className='name'>
{student.firstName.toUpperCase()}{' '}
{student.lastName.toUpperCase()}
</p>
<div className='sub-details'>
<p>Email: {student.email}</p>
<p>Company: {student.company}</p>
<p>Skill: {student.skill}</p>
<p>
Average:{' '}
{student.grades.reduce(
(a, b) => parseInt(a) + parseInt(b),
0
) /
student.grades.length +
'%'}
</p>
<button onClick={handleChange} className='showBtn'>
{btnValue}
</button>
{showData && (
<div>
<br />
{student.grades.map((grade, key) => {
return (
<p key={key}>
Test {key + 1}: {grade}%
</p>
);
})}
</div>
)}
...
Collapse Image
Expand Image
All the elements expand and collapse together because you assign to all of them the same state showData state.
One solution would be to add a new field to your data (so inside student) that is true or false when you want to expand or collapse the single student.
Another solution would be to create the showData state as an array where each element correspond to a different student. When you click the button, in this case, you pass to the function for example the id and with that you link your student to the right element inside the showData.
I'm trying to handle the onFocus and onBlur events for 2 elements - the input and the textarea elements. I even tried to implement it as one state with the object but it's just not possible, so I separated it into two states. Whenever I try to focus on the textarea, it'll expand, however, if I start focusing on the input element, it'll collapse instead of staying expanded. How do I implement this?
const [titleFocused, setTitleFocus] = useState(null);
const [contentFocused, setContentFocus] = useState(null);
function handleFocus(event) {
const {name} = event.target;
setTitleFocus(name === 'title' && true);
setContentFocus(name === 'content' && true);
}
function handleUnfocus(event) {
const {name} = event.target;
setTitleFocus(name === 'title' && false);
setContentFocus(name === 'content' && false);
}
return (
<div >
<form
className="create-note">
{titleFocused || contentFocused && (
<input
name="title"
onChange={handleChange}
value={note.title}
placeholder="Title"
onFocus={handleFocus}
onBlur={handleUnfocus}
/>)
}
<textarea
name="content"
onChange={handleChange}
value={note.content}
placeholder="Take a note..."
onFocus={handleFocus}
onBlur={handleUnfocus}
rows={titleFocused || contentFocused ? "3" : "1"}
/>
<Zoom in={titleFocused || contentFocused} appear={true}>
<Fab onClick={submitNote}>
<AddIcon fontSize="large" />
</Fab>
</Zoom>
</form>
</div>
);
Probably caused by event handles toggling both states even when the event is not concerning the component.
This should work
function handleFocus(event) {
const {name} = event.target;
name === 'title' && setTitleFocus(true);
name === 'content' && setContentFocus(true);
}
function handleUnfocus(event) {
const {name} = event.target;
name === 'title' && setTitleFocus(false);
name === 'content' && setContentFocus( false);
}
I tried to implement something like a multi-select, where the user can either select a value from a data list or can type in a new value. A chosen value should be added to an array if the user presses enter. For detecting changes in the input field I use onChange and a state variable that saves the current value typed in. For detecting the press of enter I use onKeyDown. The problem is that I'm no longer able to type something in the input field, however choosing values from the data list works. I figured out that when I comment out onKeyDown, I can type something in the input field and can also choose from values provided by the data list. However, in this case, adding values to an array on the press of enter doesn't work. I'm fairly new to React, is there something I miss?
My current code looks like follows:
const EditableMultiSelect = ({ field, helpers, metadataField, editMode, setEditMode }) => {
const { t } = useTranslation();
const [inputValue, setInputValue] = useState('');
const handleChange = e => {
const itemValue = e.target.value;
setInputValue(itemValue);
}
const handleKeyDown = event => {
event.preventDefault();
if (event.keyCode === 13) {
field.value[field.value.length] = inputValue;
helpers.setValue(field.value);
setInputValue("");
}
}
const removeItem = () => {
console.log('to be implemented');
}
return (
editMode ? (
<>
<div
onBlur={() => setEditMode(false)}
ref={childRef}>
<input name="inputValue"
value={inputValue}
type="text"
onKeyDown={e => handleKeyDown(e)}
onChange={e => handleChange(e)}
placeholder={t('EDITABLE.MULTI.PLACEHOLDER')}
list="data-list"
/>
<datalist id="data-list">
{metadataField.collection.map((item, key) => (
<option key={key}>{t(item.value)}</option>
))}
</datalist>
</div>
{(field.value instanceof Array && field.value.length !== 0) ? (field.value.map((item, key) => (
<span className="ng-multi-value"
key={key}>
{t(item)}
<a onClick={() => removeItem(key)}>
<i className="fa fa-times" />
</a>
</span>
))) : null}
</>
) : (
<div onClick={() => setEditMode(true)}>
{(field.value instanceof Array && field.value.length !== 0) ? (
<ul>
{field.value.map((item, key) => (
<li key={key}>
<span>{item}</span>
</li>
))}
</ul>
) : (
<span className="editable preserve-newlines">
{""}
</span>
)}
<i className="edit fa fa-pencil-square"/>
</div>
)
);
};
You're calling event.preventDefault() every time a key is pressed. You should move it inside the if statement:
const handleKeyDown = event => {
if (event.keyCode === 13) {
event.preventDefault();
field.value[field.value.length] = inputValue;
helpers.setValue(field.value);
setInputValue("");
}
}
you can't type anything anymore in the input text because in the handleKeyDown event handler, you're calling event.preventDefault() in the early lines. So i think you just have to move it into the if case:
const handleKeyDown = event => {
if (event.keyCode === 13) {
event.preventDefault();
field.value[field.value.length] = inputValue;
helpers.setValue(field.value);
setInputValue("");
}
}
Remove e.preventDefault() or put it inside the if statements.
It is the one preventing the input from being editable.
Within my reactjs class component, I want to create a button that opens a new text area everytime I click on it (e.g., when I click 5 times on it, it should open 5 textareas). In the current result, it only opens a textarea ones.
Thus, In a first step, I created a state with value 0 and create a function that should change the state:
// InitialState
state = {
value: 0,
};
onChange() {
this.setState({
value: this.state.value + 1,
});
}
In the next step, I rendered a button and created if-statements to show the textareas (which does not work):
render() {
return (
<div>
<IconButton onClick={this.onChange.bind(this)}>
<AddCircleOutlineIcon />
</IconButton>
<br />
{this.state.value >= 1 ? this.showTextArea() : null}
</div>
);
}
And this is my showTextArea function:
showTextArea = () => {
return (
<textarea
placeholder={this.state.placeholder}
value={this.state.value}
onChange={this.onChange1.bind(this)}
rows="2"
style={{ fontSize: "18px" }}
onFocus={(e) => (e.target.placeholder = "")}
onBlur={(e) => (e.target.placeholder = this.state.placeholder)}
/>
);
};
You condition is wrong. this.state.value >= 1 It should be like this because after first textbox opens and you click your button value will be 2 and first textbox will hide
This can be achieved using only single condition. Change your render method like this with for loop:
render() {
return (
<div>
<IconButton onClick={this.onChange.bind(this)}>
<AddCircleOutlineIcon />
</IconButton>
<br />
{
for (let i = 0; i < this.state.value; i++) {
{this.showTextArea()}
}
}
</div>
);
}