How to use hooks in multiple rendered inputs? - reactjs

I'm creating the form to my app. Problem i'm facing is whenever i click add and generate another row of inputs i got an "Rendered more hooks than during the previous render" error.
I have a "benefit" input with checkbox. When user checks it i want to render another 2 inputs (in the same row) with KPI. Hook works perfectly fine if there is only one row. When i add another row the error occurs.
const renderBenefitFields = (benefit, index, fields) => {
const [hidden, setHidden] = useState(false);
return (
<div key={index}>
<InputGroup>
<Input
name={`${benefit}.projectBenefitData`}
type="text"
component={renderField}
label="Benefit of the project"
/>
<InputGroupAddon addonType="append">
<InputGroupText>
<Input
addon
type="checkbox"
aria-label="Mesurable benefit?"
onChange={() => setHidden(!hidden)}
/>
</InputGroupText>
<InputGroupText>Benefit mesurable </InputGroupText>
</InputGroupAddon>
<Trash
className="align-middle"
size={25}
onClick={() => fields.remove(index)}
/>
Show KPI inputs? {hidden === false ? "false" : "true"}
</InputGroup>
</div>
);
};
const renderBenefits = ({ fields }) => (
<div>
{fields.map(renderBenefitFields)}
<PlusCircle
className="align-baseline"
size={24}
onClick={() => fields.push({})}
/>
</div>
);
Perfect solution would be: when user checks the box react will show another two inputs.

As Ross Hunter mentioned,
1. You should capitalize your component, i.e. RenderBenefitFields
2. No mutation on state/props, in onClick={() => fields.push({})}
In addition, you should call your RenderBenefitFields in renderBenefits as following:
{fields.map((props, index) => <RenderBenefitFields key={index} {...props} />)}
P.S. You can use a unique key instead of index, depending on your situation.

The name of user-defined components needs to be capitalized.
The onClick callback in PlusCircle looks like it's directly mutating state. That's a big problem for React, and almost certainly the root of your issue. I can explain further if you need sometime I'm not on mobile 😊

Related

React setting specific input forms when mapping through array

so I have fetched a bunch of students data from an api and mapped through it, displayed the student info in divs as per common react practices.
I want to add a text input field to be able to add tags/comments to every instance of student. Code looks something like this
<form onSubmit={(e) => handleSubmit(e, i)}>
<input
type='text'
placeholder='Add a tag'
className='tag__input'
value={tag}
onChange={(e) => setTag(e.target.value)}
/>
</form>
const [tag, setTag] is just a useState holding the string value of the comment I want to add.
this is expectedly not working as I need because the value of all my input fields is changing, whereas I need only the one specific one to change based on which student I need to leave a comment for. What would be the next steps into setting this logic up? thanks!
You should create separate component for student info and input, and put inside of that component useState.
Now you have 1 state for all fields.
UPD, something like
function StudentEntry({ data, onSubmit }) {
// one state per entry
const [tag, setTag] = React.useState('')
return (<>
<StudentInfo {...data} />
<form onSubmit={(e) => onSubmit(e, data.id)}>
<input
type='text'
placeholder='Add a tag'
className='tag__input'
value={tag}
onChange={(e) => setTag(e.target.value)}
/>
</form>
</>
}
// and use somewhere
{studentsData.map(data =>
<StudentEntry data={data} key={data.id} onSubmit={handleSubmit} />
)}

Is there a way to add a class to all elements that has a property set to true?

I have an object with a property of questions. questions is an array of 13 objects.Each object has a choices array that have between 2-4 objects and each object has an isCorrect property that is either true or null.
All my questions are being rendered. I have a button that I want to add a classname to each correct answer that the user selected. Do you guys have any suggestions of how I can achieve this?
{quiz.questions.map((question, index) => (
<ContentWrapper key={question._key}>
<h3>
{`${index + 1}. `}
{question.title}
</h3>
{question.choices.map(choice => (
<ChoiceStyle id={"wrapper" + choice._key} key={choice._key}>
<input
type="radio"
id={choice._key}
name={question._key}
onChange={updateScore}
value={choice.isCorrect === null ? "0" : "1"}
/>
<label htmlFor={choice._key}>{choice.title}</label>
</ChoiceStyle>
))}
<ButtonComponent
type="button"
buttonType="second"
title="Sjekk svar"
onClick={() => {
calculateScore();
}}
/>
That button just calculates the score but I might need to add another function to that button? I am also using styled components
You have multiple approaches to do this, the easiest and less invasive way is to add a ternary condition to show/hide the class name based on an isCorrect property.
I'm assuming that quiz is a state that is changed in each onChange to refresh all the real-time changes on the quiz. At this point:
{quiz.questions.map((question, index) => (
<ContentWrapper key={question._key}>
<h3>
{`${index + 1}. `}
{question.title}
</h3>
{question.choices.map(choice => (
<ChoiceStyle id={"wrapper" + choice._key} key={choice._key}>
<input
type="radio"
id={choice._key}
name={question._key}
onChange={updateScore}
className={`${choice.isCorrect ? 'is-valid':''}`}
value={choice.isCorrect === null ? "0" : "1"}
/>
<label htmlFor={choice._key}>{choice.title}</label>
</ChoiceStyle>
))}
<ButtonComponent
type="button"
buttonType="second"
title="Sjekk svar"
onClick={() => {
calculateScore();
}}
/>
The trick is the following ternary condition:
className={`${choice.isCorrect ? 'is-valid':''}`}
The problem that occurs is that the class gets applied right away. I
don't want the correct answer to show up unless the user has clicked
the button component
Then you will need to add a new property to your choice object to check and play with it. Let's assume something like:
const choice={
key: "some key"
isCorrect: true
/// other fields
}
// more code
{quiz.questions.map((question, index) => (
<ContentWrapper key={question._key}>
<h3>
{`${index + 1}. `}
{question.title}
</h3>
{question.choices.map(choice => (
<ChoiceStyle id={"wrapper" + choice._key} key={choice._key}>
<input
type="radio"
id={choice._key}
name={question._key}
onChange={updateScore}
className={`${choice.isValid ? 'is-valid':'' }`}
value={choice.isSelected === null ? "0" : "1"}
/>
<label htmlFor={choice._key}>{choice.title}</label>
</ChoiceStyle>
))}
<ButtonComponent
type="button"
buttonType="second"
title="Sjekk svar"
onClick={() => {
calculateScore();
}}
/>
In each updateScore, you'll need to check if the choice is correct and matches your business logic and add the new property (isValid) to trigger it.
const updateScore =()=>{
// some unknown logic
// some validations and then:
choice.isValid = true,
}
Since (I've assumed) that you are setting the quiz in React's state, the new property will force a refresh of the state, applying the class name in the user clicks.

Navigate with tabIndex when elements where hidden

Summary
I want to understand how the tabulation navigation works on browser, in order to perform custom input with suggestion field on React.
Objective
The objective is to provide 2 custom input to the use with suggestion dropdown. When I click to my input, the dropdown shows correctly and I can navigate through the tabulation key to my suggestions. But when I try to navigate to the next input, the dropdown shows but I can't navigate through my second dropdown suggestion.
Code example
I have the following JSX code:
class InputDropdown React.Component {
render() {
return(<div>
<div id="input1" className="input-wrapper" onMouseEnter={...} onMouseLeave={...} onBlur={...}>
<input className={"input"}
value={this.state.search_input}
onKeyDown={(e) => e.keyCode==13?this.props.validate(this.state.search_input)}
onFocus={this.setState(display_dropdown_input1: true)}
/>
<div className={"dropdown-wrapper" + this.state.display_dropdown_input1?"":"hidden"}>
{this.props.suggestion.map((suggestion, index) =>
<div className="suggestion"
tabIndex={0}
onKeyDown={(e) => e.keyCode==13?this.props.validate(suggestion.text)}
onBlur={index+1==this.props.suggestion.length?this.setState({display_dropdown_input1:false})}
>{suggestion.text}</div>)}
</div>
</div>
<div id="input2" className="input-wrapper" onMouseEnter={...} onMouseLeave={...} onBlur={...}>
<input className={"input"}
value={this.state.search_input}
onKeyDown={(e) => e.keyCode==13?this.props.validate(this.state.search_input)}
onFocus={this.setState(display_dropdown_input2: true)}
/>
<div className={"dropdown-wrapper" + this.state.display_dropdown_input2?"":"hidden"}>
{this.props.suggestion.map((suggestion, index) =>
<div className="suggestion"
tabIndex={0}
onKeyDown={(e) => e.keyCode==13?this.props.validate(suggestion.text)}
onBlur={index+1==this.props.suggestion.length?this.setState({display_dropdown_input2:false})}
>{suggestion.text}</div>)}
</div>
</div>
</div>)
}
}
Question
It's as if the tabulation navigations road is prepared on the first tabulation pressed and if element appear after this press they are not taken in count.
Is it possible to perform it in React?

ReactiveSearch only autocomplete

I want to use ReactiveSearch library only for autocomplete with submit.
const Search = () => (
<div className="search-field">
<ReactiveBase
app="good-books-ds"
credentials="nY6NNTZZ6:27b76b9f-18ea-456c-bc5e-3a5263ebc63d"
>
<div className="row">
<div className="col">
<DataSearch
dataField={['original_title', 'original_title.search']}
categoryField="authors.raw"
componentId="BookSensor"
/>
</div>
</div>
</ReactiveBase>
</div>
)
export default Search
I tried making the input as above with <DataSearch ... /> and it works, but it doesn't have submit option. I tried to wrap it with form, but after enter or select value it doesn't fire.
Any suggestions?
https://opensource.appbase.io/reactive-manual/search-components/datasearch.html
you need to read the doc carefully there is onValueChange handler so when you type in something you can set the state first set the initial state state = {searchText: ""} at the top after that in Data search prop you can do the following
<DataSearch onValueChange = {(e) => this.setState({searchText: value})} />
now make you own button and submit the value in the state for example this.state.searchText
ReactiveSearch now supports an onValueSelected prop which is perfect for usecases where you are only interested in utilizing the selected value (either selecting a suggestion or hitting Enter key). Docs and example usage:
<DataSearch
...
onValueSelected={(value) => console.log('The selected value is', value)}
/>

reactjs - how to get focused virtual element

I have an input list. And want to know how to get current focused inputbox:
{list.map((item, index) => {
return <div>
<label>{item.name}</label>
<input {...item} />
</div>
})}
My thought is adding onFocus and onBlur function in each inputbox and saved in state. Is that a better way for this?

Resources