React form - Field is automatically unselected when value changes - javascript

I'm a total beginner in React, and am trying to build my first form.
I fetch the questions asked in said form using an API, and once the form submitted, I would like to create an answer object through this same API.
The problem is that everything seems to work just fine (questions render ok, answer object is updated in state), but everytime the value of a field change, I have to re-select the field to keep typing. Basically, it's like I'm auto-clicked away from the field when the value changes.
Here's what happens :
And here's a snippet of (probably) the offending code :
class Form extends React.Component {
constructor(props) {
super(props);
{/* Questions are filled on page load, answers is what I'm working with */}
this.state = { questions: [], answers: [] };
this.handleChange = this.handleChange.bind(this);
}
// This is where the magic is supposed to happen
handleChange(event) {
let key = event.target.id,
value = event.target.value;
{/* Here, my goal is to build an object with questions ids as keys and values of fields as key values. */}
this.setState(prevState => {
let copy = Object.assign({}, prevState.answers);
copy[key] = value;
return { answers: copy };
})
}
render() {
const { questions } = this.state;
const TextareaInput = (fieldId) => (
<div>
<textarea name={ fieldId.fieldId } value={this.state.answers[fieldId.fieldId]} id={ fieldId.fieldId } onChange={this.handleChange} ></textarea>
</div>
);
const TextInput = (fieldId) =>(
<div>
<input type='text' name={ fieldId.fieldId } value={this.state.answers[fieldId.fieldId]} id={ fieldId.fieldId } onChange={this.handleChange} />
</div>
);
const allQuestions = (
questions.map((q, key) =>
<div key={q.id} className='question'>
<label htmlFor={ q.field_id } className={ q.required ? 'required' : '' } dangerouslySetInnerHTML={{__html: q.question}}></label>
{q.field_type == 'text' ? <TextInput fieldId={q.field_id}/> : <TextareaInput fieldId={q.field_id}/>}
</div>
)
)
return (
<form>
{ allQuestions }
</form>
)
}
}
export default Form;
(Full component on pastebin)
I think the problem comes from my handleChange function, but I'm not sure what could be causing this. I tried adding some stuffs and moving things around a little without any luck...

You need to call the TextInput and TextareaInput like functions instead of using them like separate components since you defined them within the component.
{q.field_type == 'text'
? TextInput(q.field_id)
: TextareaInput(q.field_id)}
React was unable to keep the reference to them straight and seemingly considered them different elements every render.
Also, as I'm sure you are already aware, you should be careful using dangerouslySetInnerHTML as the name implies, it can be dangerous.
class Form extends React.Component {
constructor(props) {
super(props);
{
/* Questions are filled on page load, answers is what I'm working with */
}
this.state = {
questions: [
{
id: 2,
field_id: 2,
question: 'How are you today?',
field_type: 'text',
},
{
id: 3,
field_id: 3,
question: 'What\'s the answer to life, the universe, and everything??',
field_type: 'textarea',
},
],
answers: [],
};
this.handleChange = this.handleChange.bind(this);
}
// This is where the magic is supposed to happen
handleChange(event) {
let key = event.target.id,
value = event.target.value;
{
/* Here, my goal is to build an object with questions ids as keys and values of fields as key values. */
}
this.setState((prevState) => {
let copy = Object.assign({}, prevState.answers);
copy[key] = value;
return { answers: copy };
},()=>{console.log(this.state)});
}
render() {
const { questions } = this.state;
const TextareaInput = (fieldId) => (
<div>
<textarea
name={fieldId}
value={this.state.answers[fieldId]}
id={fieldId}
onChange={this.handleChange}
></textarea>
</div>
);
const TextInput = (fieldId) => (
<div>
<input
type="text"
name={fieldId}
value={this.state.answers[fieldId]}
id={fieldId}
onChange={this.handleChange}
/>
</div>
);
const allQuestions = questions.map((q, key) => (
<div key={q.id} className="question">
<label
htmlFor={q.field_id}
className={q.required ? 'required' : ''}
// As I'm sure you are already aware, this is likely a terrible idea.
dangerouslySetInnerHTML={{ __html: q.question }}
></label>
{q.field_type == 'text'
? TextInput(q.field_id)
: TextareaInput(q.field_id)}
</div>
));
return <form>{allQuestions}</form>;
}
}
ReactDOM.render(<Form/>, document.querySelector('#root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root" />
In order to avoid using dangerouslySetInnerHTML for the questions, I'd suggest using some sort of markdown renderer. It should be a good enough for most use cases in asking questions.
https://www.npmjs.com/package/react-markdown
https://www.markdownguide.org/
class Form extends React.Component {
constructor(props) {
super(props);
{
/* Questions are filled on page load, answers is what I'm working with */
}
this.state = {
questions: [
{
id: 2,
field_id: 2,
question: 'How are *you* today?',
field_type: 'text',
},
{
id: 3,
field_id: 3,
question: 'What\'s the **answer** to life, the universe, and everything??',
field_type: 'textarea',
},
{id: 4,
field_id: 4,
field_type: 'text',
question:`# This is the big question
#### *ARE YOU READY?*
1. Is this the real life?
1. Or is this just fantasy?
`
}
],
answers: [],
};
this.handleChange = this.handleChange.bind(this);
}
// This is where the magic is supposed to happen
handleChange(event) {
let key = event.target.id,
value = event.target.value;
{
/* Here, my goal is to build an object with questions ids as keys and values of fields as key values. */
}
this.setState((prevState) => {
let copy = Object.assign({}, prevState.answers);
copy[key] = value;
return { answers: copy };
},()=>{console.log(this.state)});
}
render() {
const { questions } = this.state;
const TextareaInput = (fieldId) => (
<div>
<textarea
name={fieldId}
value={this.state.answers[fieldId]}
id={fieldId}
onChange={this.handleChange}
></textarea>
</div>
);
const TextInput = (fieldId) => (
<div>
<input
type="text"
name={fieldId}
value={this.state.answers[fieldId]}
id={fieldId}
onChange={this.handleChange}
/>
</div>
);
const allQuestions = questions.map((q, key) => (
<div key={q.id} className="question">
<label
htmlFor={q.field_id}
className={q.required ? 'required' : ''}
><ReactMarkdown source={q.question}/></label>
{q.field_type == 'text'
? TextInput(q.field_id)
: TextareaInput(q.field_id)}
</div>
));
return <form>{allQuestions}</form>;
}
}
ReactDOM.render(<Form/>, document.querySelector('#root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-markdown/4.3.1/react-markdown.js" integrity="sha256-4jDgUokdWbazrdnMjWm+TftvBFnOwSNIpvKhgYsInfw=" crossorigin="anonymous"></script>
<div id="root" />
If you need full capabilities of rendering:
https://pragmaticwebsecurity.com/files/cheatsheets/reactxss.pdf, gives an example to use DOMPurify to sanitize the input coming in to prevent cross site scripting and other dangerous behavior rendering outside html can give.
So, for that, you'd purify the string and then pass it into dangerouslySetInnerHTML once it's purified.

I think the questions get lost from your state due to the setState call. AFAIK if setState is given an updater function, it must return the whole state. Shallow merging is only supported if an object is given.
this.setState(prevState => ({
...prevState,
answers: {
...prevState.answers,
[key]: value;
}
}))
(I hope object spread is supported in your environment, If not, do yourself a favor ;-))

Related

Change React component visibility based on the state of another component

I have created the following React component. It uses an input box to accept a user's answer to a riddle. As soon as the user's input matches the desired answer, the input box become read-only (a bit of a strange way to use them). It also has an "isHidden" prop to determine whether the riddle is rendered.
class Riddle extends React.Component {
constructor(props) {
super(props);
this.answer = props.answer.toUpperCase();
this.state = {
text: "",
isAnswered: false
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
let userInput = event.target.value.toUpperCase();
if (userInput == this.answer) {
this.setState({
text: userInput,
isAnswered: true
});
} else {
this.setState({
text: userInput,
isAnswered: false
});
}
}
render() {
if (this.props.isHidden) {
return <div></div>;
} else {
return (
<div>
<p>{this.props.prompt}</p>
<input type="text" value={this.state.text}
readOnly={this.state.isAnswered}></input>
</div>
);
}
}
}
Here it is in practice:
function App() {
return (
<div>
<Riddle prompt='The first three letters in the alphabet.' answer="abc" isHidden="false"/>
</div>
);
}
ReactDOM.render(<App />, document.getElementById('root'));
What I would like to do is have a bunch of these riddles in sequence, but have riddles only be visible when the previous one was solved. The trouble is that I don't know how to cause the visibility update to happen.
I have read about lifting state from children to a parent component, and I've tried to see if I could create a RiddleSequence component with Riddles as its children, and have RiddleSequence manage visibility. My problem is that currently it is part of Riddle's state whether or not it's solved, and I don't know how RiddleSequence can read that information since child state should remain hidden. This seems like a reasonable way to encapsulate Riddle's functionality, but maybe I'm wrong given my goals.
I have also considered making Riddles be children of other riddles they depend on, since I can just pass state/props to children:
<Riddle prompt="first riddle"...>
<Riddle prompt="depends on first riddle"...>
<Riddle prompt="depends on second riddle"...>
</Riddle>
</Riddle>
</Riddle>
But if I have an app with 100 riddles, this seems to get ridiculous. This also reduces flexibility for a more expanded set of features (such as making one riddle depend on a group of 3 riddles).
How can I make the visibility of my Riddle components depend on the state of other riddles?
A simple solution would be to have a container component as you said:
class Riddle extends Component {
constructor(props) {
this.state = {
text: ''
}
this.answer = props.answer.toUpperCase()
}
handleChange = event => {
const userInput = event.target.value.toUpperCase()
const callback = userInput == this.answer ? this.props.onSolved : undefined
this.setState({ text: userInput }, callback)
}
render() {
const { text, isAnswered } = this.state
const { prompt } = this.props
if (this.props.isHidden) {
return null
}
return (
<div>
<p>{prompt}</p>
<input type="text" value={text} readOnly={isAnswered}></input>
</div>
)
}
}
and container should hold visibility like this:
class RiddleSequence extends Component {
state = {}
riddles = [
{
id: 1,
prompt: 'The first three letters in the alphabet.',
answer: 'abc',
prev: null
},
{
id: 2,
prompt: 'The last three letters in the alphabet.',
answer: 'xyz',
prev: 1
}
]
render() {
return (
<div>
{this.riddles.map(r => {
const { id, prev } = r
const visible = !prev || this.state[prev]
return (
<Riddle
key={id}
isHidden={!visible}
onSolved={() => this.setState({ [r.id]: true })}
{...r}
/>
)
})}
</div>
)
}
}

Getting a value of inputs populated Dynamically React.js

I am pretty new to React, I have worked on react native before, so I am quite familiar with a framework. Basically I have an array of objects, lets say in contains 5 items. I populated views based on the amount of objects, so if there are 5 objects, my map function would populate 5 together with 5 inputs. My question is how can I get a value of each input?
Here is my code:
array.map(map((item, index) => (
<h1> item.title </h1>
<input value={input from user} />
)
You have to use the state and update the value with onChange manually
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
value: ''
}
}
handleInputChange(e) {
this.setState({
[e.target.name]: e.target.value
});
}
render () {
return (
<div>
<input value={this.state.value} onChange={(e) => {this.handleInputChange(e)}} />
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('app'))
A quick solution would be to use an array for all the input values.
const Inputs = ({array}) => {
const [inputs, setInputs] = useState([]);
const setInputAtIndex = (value, index) => {
const nextInputs = [...inputs]; // this can be expensive
nextInputs.splice(index, 1, value);
setInputs(nextInputs);
}
return (
...
array.map((item, index) => (
<div key={index}>
<h1>{item.title}</h1>
<input
value={inputs[index]}
onChange={({target: {value}) => setInputAtIndex(value, index)}
/>
</div>
)
...
);
}
Keep in mind here that in this case every time an input is changed, the inputs state array is copied with [...inputs]. This is a performance issue if your array contains a lot of items.

React - Dynamically set state without hardcoding key field

In ES6, ComputedPropertyName allows us to do things like use a variable as a key, which in turn means we can set state dynamically. However, if you look around at examples of setting state dynamically, they tend to all have one thing in common -- the state key's name is hardcoded. As an example:
class Input extends React.Component {
state = { state1: "" };
handleChange = event => {
const {
target: { name, value }
} = event;
this.setState({
[name]: value
});
};
render() {
return (
<div>
<label>
<input
type="text"
name="state1"
value="new value"
onChange={this.handleChange}
/>
</label>
</div>
);
}
}
This works because we have a state key called "state1", as seen in the line state = { state1: "" };, and we are hardcoding name in the input field to be that state key, as seen in the line name="state1".
I do not like this solution, because it means I now have to keep track of state.state1" in more than one location. If I were to refactorstate.state1to instead bestate.state2, I would have to go findname="state1"1 and update that to read name="state2". Instead of worry about that, I am wondering if there is a way to set state dynamically without hardcoding this state key. That is, I'm looking to change
<input
type="text"
name="state1"
value="new value"
onChange={this.handleChange}
/>
Into something like:
<input
type="text"
name={this.state.state1.keyname}
value="new value"
onChange={this.handleChange}
/>
Obviously the above doesn't work because keyname is undefined, but the intention here is that name can take on the value of "state1" without me having to hardcode it. How can this be achieved?
You can have an array with objects with keys type and name which you can use to set the initial state and render the inputs dynamically. This way you'll only have to change the value once in the array. You can do something like this.
Here is a codesandbox
import React from "react";
import ReactDOM from "react-dom";
class App extends React.Component {
constructor() {
super();
this.arr = [
{ type: "text", name: "state1" },
{ type: "password", name: "state2" }
];
// set the state keys dynamically from this.arr
this.state = this.arr.reduce((agg, item) => {
agg[item.name] = "";
return agg;
}, {});
}
handleChange = event => {
const {
target: { name, value }
} = event;
this.setState(
{
[name]: value
}
);
};
renderInputs = () => {
return this.arr.map((item, i) => (
<div key={i}>
<label>
<input
type={item.type}
name={item.name}
value={this.state[item.name]}
onChange={this.handleChange}
/>
</label>
</div>
));
};
render() {
const inputs = this.renderInputs();
return <div>{inputs}</div>;
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Hope this helps !
There is the new useReducer() that comes with hooks and context. Check this out i think that is the best pattern to solve your issue. https://reactjs.org/docs/hooks-reference.html.

In React compare two array of object then check checkbox accordingly

In React I'm creating a multiple choice questionnaire. Checkboxes are generated with the possible answers. When the user ticks the answers and reloads the page, the chosen answers' checkboxes do not retained their checked state.
The questions and answers are fetched from database as an array of objects on 1st load. The user can tick multiple checkboxes for a question. A 2nd array is created that includes all the multiple answers that user has chosen and sent to database has objects. On reload, this 2nd array is added to the state of the component as well as the 1st array.
Component
const Checkbox = ({ id, name, options, onChange }) => {
return (
<div className="checkbox-group">
{options.map((option, index) => {
<div key={index}>
<label htmlFor={id}>
<input
type="checkbox"
name={name}
id={id}
value={option}
onChange={onChange}
/>
{option}
</label>
</div>
}
</div>
);
}
class Form extends Component {
constructor(props) {
super(props);
this.state = {
questionnaire: [],
answeredQuestions: [],
formData: {},
};
this.handleChange = this.handleChange.bind(this);
}
async componentDidMount() {
// it doesn't matter how I fetch the data, could have been axios, etc...
let questionnaire = await fetch(questionnaireUrl);
let answeredQuestions = await fetch(answeredQuestionsUrl);
this.setState({ questionnaire, answeredQuestions });
}
render() {
return (
<div className="questionnaire-panel">
<h1>Quiz</h1>
{this.state.questionnaire.map((question, index) => {
return (
<div key={index}>
<Checkbox
options={questions.answers}
checked={// this where I'm stuck on what to do}
name="the-quiz"
id={`the-quiz_num_${index + 1}`}
onChange={this.handleChange}
/>
</div>
)
})}
</div>
);
}
handleChange(event) {
let target = event.target;
let value = target.value;
let name = target.name;
let chosenAnwersArray = [];
let chosenAnswer = {
answer: value,
checked: true,
};
if (this.state.questionnaire.includes(chosenAnswer)) {
newChosenAnwersArray = this.state.questionnaire.filter(q => {
return q.answer !== chosenAnswer.answer;
});
} else {
newChosenAnwersArray = [...newChosenAnwersArray, chosenAnswer];
}
this.setState(prevState => ({
formData: {
[name]: value,
},
answeredQuestions: newChosenAnwersArray
}));
}
}
I want to compare these 2 arrays that are in the this.state, that if the answers in the array2 are in array1 then check the corresponding checkboxes. Is there is a better way, please teach me!

Toggling visibility of array of stateless react components

I am trying to simply map over some data returned from an api and create a stateless component for each object returned. I want to be able to click on any of the components to toggle visibility of the rest of its data.
I have tried numerous ways to do it and keep hitting a brick wall, i've also scoured stack overflow and cannot seem to find an answer.
I have gotten it working by making them individual class components, however it seems like a lot of unnecessary code for just a toggle functionality.
Thank you in advance for any help or insight, here is a quick breakdown of what I have currently.
For clarification this is a simple app for me to learn about using react and an external api, it is not using redux.
fetched users in state of class component
class PersonList extends Component {
constructor(props) {
super(props);
this.state = {
resource: []
};
}
async componentDidMount() {
let fetchedData = await API_Call("people");
this.setState({ resource: fetchedData.results });
while (fetchedData.next) {
let req = await fetch(fetchedData.next);
fetchedData = await req.json();
this.setState({
resource: [...this.state.resource, ...fetchedData.results]
});
}
}
}
Then map over the results and render a component for each result
render() {
const mappedPeople = this.state.resource.map((person, i) => (
<Person key={i} {...person} />
));
return <div>{mappedPeople}</div>;
}
Is there i can make each person component a stateless component with the ability to click on it and display the rest of the data? Here is what I have currently.
class Person extends Component {
constructor(props) {
super(props);
this.state = {
visibility: false
};
}
toggleVisible = () => {
this.setState(prevState => ({
visibility: !prevState.visibility
}));
};
render() {
return (
<div>
<h1 onClick={this.toggleVisible}>{this.props.name}</h1>
{this.state.visibility && (
<div>
<p>{this.props.height}</p>
</div>
)}
</div>
);
}
}
Again thanks in advance for any insight or help!
You could keep an object visible in your parent component that will have keys representing a person index and a value saying if the person is visible or not. This way you can toggle the person's index in this single object instead of having stateful child components.
Example
class PersonList extends Component {
constructor(props) {
super(props);
this.state = {
resource: [],
visible: {}
};
}
// ...
toggleVisibility = index => {
this.setState(previousState => {
const visible = { ...previousState.visibile };
visible[index] = !visible[index];
return { visible };
});
};
render() {
const mappedPeople = this.state.resource.map((person, i) => (
<Person
key={i}
{...person}
visible={this.state.visible[i]}
onClick={() => this.toggleVisibility(i)}
/>
));
return <div>{mappedPeople}</div>;
}
}
const Person = (props) => (
<div>
<h1 onClick={props.onClick}>{props.name}</h1>
{props.visible && (
<div>
<p>{props.height}</p>
</div>
)}
</div>
);
Similar idea with #Tholle but a different approach. Assuming there is an id in the person object we are changing visibles state and toggling ids.
class PersonList extends React.Component {
constructor(props) {
super(props)
this.state = {
resource: this.props.persons,
visibles: {},
}
}
toggleVisible = id => this.setState( prevState => ({
visibles: { ...prevState.visibles, [id]: !prevState.visibles[id] },
}))
render() {
const mappedPeople =
this.state.resource.map((person, i) =>
<Person
key={person.id}
visibles={this.state.visibles}
toggleVisible={this.toggleVisible}
{...person}
/>
)
return (
<div>
{mappedPeople}
</div>
)
}
}
const Person = (props) => {
const handleVisible = () =>
props.toggleVisible( props.id );
return (
<div>
<h1 onClick={handleVisible}>
{props.name}</h1>
{props.visibles[props.id] &&
<div>
<p>{props.height}</p>
</div>
}
</div>
);
}
const persons = [
{ id: 1, name: "foo", height: 10 },
{ id: 2, name: "bar", height: 20 },
{ id: 3, name: "baz", height: 30 },
]
const rootElement = document.getElementById("root");
ReactDOM.render(<PersonList persons={persons} />, rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
You can make sure your "this.state.resource" array has a visibility flag on each object:
this.state.resource = [
{ ..., visibility: true },
{ ..., visibility: false}
...
];
Do this by modifying your fetch a little bit.
let fetchedData = await API_Call("people");
this.setState({
resource: fetchedData.results.map(p => ({...p, visiblity: true}))
});
Merge your Person component back into PersonList (like you are trying to do), and on your onclick, do this:
onClick={() => this.toggleVisible(i)}
Change toggleVisible() function to do the following.
toggleVisible = (idx) => {
const personList = this.state.resource;
personList[idx].visibility = !personList[idx].visibility;
this.setState({ resource: personList });
}
So now, when you are doing:
this.state.resource.map((person, i) => ...
... you have access to "person.visibility" and your onclick will toggle the particular index that is clicked.
I think that directly answers your question, however...
I would continue with breaking out Person into it's own component, it really is good practice!
Other than better organization, one of the main reason is to avoid lamdas in props (which i actually did above). Since you need to do an onClick per index, you either need to use data attributes, or actually use React.Component for each person item.
You can research this a bit here:
https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md
BTW you can still create "components" that aren't "React.Component"s like this:
import React from 'react';
const Person = ({ exProp1, exProp2, exProp3}) => {
return <div>{exProp1 + exProp2 + exProp3}</div>
}
Person.propTypes = {
...
}
export default Person;
As you can see, nothing is inheriting from React.Component, so you are getting the best of both worlds (create components without creating "Components"). I would lean towards this approach, vs putting everything inline. But if your application is not extremely large and you just want to get it done, going with the first approach isn't terribly bad.

Categories