ReactJS: Trying to get values inside an array inside of state - javascript

So, I am using antd to handle a Form component that has an initial state like this:
...constructor
...super
state = {
tags: ["tag"]
}
handleSubmit = e => {
e.preventDefault();
console.log(this.state);
// gives me Object { tags: [] }
}
render() {
return <Form onSubmit={this.handleSubmit} />
}
This is literally it... but I have no idea why I keep getting an empty array in console. Maybe this is impossible to figure out with this much info, but that's why it's driving me insane. I have noticed that if I comment out some code in this class, that it will all of sudden start showing the array values... no idea.
Any help would be appreciated.
When I go into the React Devtools, it shows the "tag" in my tags array. But, when I hit submit, it clears the array. I'm guessing this is tied in somehow, but I'm not sure why the state is clearing just the tags array and not the other state values. Something to do with a deep clone... ?

For setup default values in ant design form and after validate you need to follow the required steps:
Wrap form:
class YouComponent extends Component { //..... }
const WrappedComponent = Form.create({ name: 'you_form' })(YouComponent);
export default WrappedComponent;
To setup default values:
in you case i think you can use the Select component for tags
<Form layout="inline" onSubmit={this.handleSubmit}>
<Form.Item>
{getFieldDecorator("tags", {
rules: [{ required: true, message: "Pleases select tag!" }]
})(
<Select
mode="multiple"
style={{ width: "100%" }}
placeholder="Please select"
// here setup default selected of tags
defaultValue={this.state.tags}
>
{// map here tags <Select.Option>}
</Select>
)}
</Form.Item>
</Form>
Select documentation
To validate and get value from form:
handleSubmit = e => {
e.preventDefault();
this.props.form.validateFields((err, values) => {
if (!err) {
console.log("Received values of form: ", values);
}
});
};

Related

Checkbox state gets empty after page change

I have an array of objects that looks like this:
const columns = [
{
key: "Source_campname",
title: "TS Camp Name",
customElement: function (row) {
return (
<FormControlLabel
control={
<Checkbox
checked={checkbox[row.id]}
key={row.id}
onChange={() =>
handleChange(row.Source_campname, row.id, checkbox)
}
name={row.id}
/>
}
label={[row.Source_campname]}
/>
);
}
},
{
key: "Tracker_campname",
title: "TR Camp Name"
}
];
You can see a "handleChange" function above, this is used to check/uncheck the component
The handleChange function looks like this:
const handleChange = (name, campid) => {
setCheckBox({ ...checkbox, [campid]: !checkbox[campid] });
};
You can also see a "customElement" function above. This function is rendered in another React component named ThanosTable. I will just write down part of the code where the rendering of customElement happens below.
return (
<> columnArray[0].customElement(row) </>
);
In the end you get 10 checkboxes, and you have a few pages that can be changed using pagination.
Do check my codesandbox link here for a working example:
https://codesandbox.io/s/magical-germain-8tclq
Now I have two problems:
Problem 1) If I select a few checkboxes, then go to second page and return, the checkbox state is empty and the original checkboxes are unselected. No idea why that is happening. How do I prevent that?
Problem 2) The value of checkbox state is always an empty object ({}) inside customElement function. You can see this by checking console.log(checkbox) inside customElement function (Check Line 76 in codesandbox). I thought it should be an array with selected checkbox items.
The useEffect hook embodies all the lifecycle events of a component. Therefore if you try to set checkbox in useEffect it'll infinitely update the component because updating state calls useEffect. This is probably why you see your state constantly being reset.
Instead, initialize your state with the rows before rendering.
const rows = [
...
];
let checkboxObj = {};
// if (rows) {
rows.forEach((e) => {
checkboxObj[e.id] = false;
});
const [checkbox, setCheckBox] = useState(checkboxObj);

react dynamic form values with antd

Dynamic forms with react and antd are eluding me. I have scoured the web looking for answers to no avail. Here is a codepen with a recreation of the issue I am having: https://codepen.io/sethen/pen/RwrrmVw
Essentially, the issue boils down to when you want loop through a bunch of values that are stored in state, like so:
class MyClass extends React.Component<{}, {}> {
constructor(props) {
super(props);
this.state = {
data: [
{ name: 'foo' },
{ name: 'bar' },
{ name: 'baz' }
]
};
}
You can think of these values as being fetched from some remote API.
As you can see, I have an array of objects with the key of name in the state. Further on down in the render cycle is the following:
return data.map((value, index) => {
const { name } = value;
return (
<Form key={ index } initialValues={ { name } }>
<Form.Item name='name'>
<Input type='text' />
</Form.Item>
<Button onClick={ this.handleOnDeleteClick.bind(this, index) }>Delete</Button>
</Form>
);
This attempts to loop through the values stored in the state and put the values into an input. It also adds a little delete button to get rid of that item. The first time it renders, it does as you expect it to loading the value into the input value.
The issue is when you try to delete one of the items, like the middle one, it will delete the next item. The core of the issue is that the render is acting different than I expect it to when deleting an item. I am expecting that when I delete an item, it will take it out of state and load the ones that are left. This is not happening.
My question is, how am I able to load dynamic data in this way with antd whilst being able to delete each item?
The main mistake in this form that you assign the key property as the array index, and on deleting the middle item, the last component will get a new key.
In React, changing the key will unmount the component and lose its state.
Don’t pass something like Math.random() to keys. It is important that keys have a “stable identity” across re-renders so that React can determine when items are added, removed, or re-ordered. Ideally, keys should correspond to unique and stable identifiers coming from your data, such as post.id.
Also, in your example, you actually render three forms instead of a single form and three fields.
Every <form/> has in its inner state all states of its form fields, so you will have a single object with all input values in it.
Antd.Form just a wrapper for such form, you can get Form.Item values in onFinish callback for example.
class MyClass extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [{ name: "foo" }, { name: "bar" }, { name: "baz" }]
};
}
handleOnDeleteClick = index => {
this.setState({
data: [
...this.state.data.slice(0, index),
...this.state.data.slice(index + 1)
]
});
};
render() {
const { data } = this.state;
return (
<Form>
{data.map(({ name }, index) => {
return (
<Form.Item key={name}>
<Input type="text" />
<Button onClick={() => this.handleOnDeleteClick(index)}>
Delete
</Button>
</Form.Item>
);
})}
</Form>
);
}
}

React: setting a state Hook causes error - Too many re-renders. React limits the number of renders to prevent an infinite loop

In React, I have created a Component to allow the user to submit a book to add to a list of books. The site visually looks like this (the AddBook component is framed in red):
I will share my full code further below, but first I just need to explain the problem. In the AddBook component, the "select author" dropdown menu (that you see in the screenshot above) is using <select> <option> elements, which allows the user to select from a list of authors, before submitting the form to add a book. The list of authors are fetched from a GraphQL API call.
snippet code:
function displayAuthors() {
if (loading) return (<option disabled>'Loading authors...'</option>);
if (error) return (`Error: ${error.message}`);
return data.authors.map((author) => {
return (
<option key={author.id} value={author.id}>{author.name}</option>
);
});
}
return (
<form id="add-book" onSubmit={submitBook}>
<h1>Add a new book to Wiki Books</h1>
<div className="field">
<label>Book name: </label>
<input type="text" onChange={handleChange} name="name" required value={bookEntry.name}/>
</div>
<div className="field">
<label>Genre: </label>
<input type="text" onChange={handleChange} name="genre" required value={bookEntry.genre}/>
</div>
<div className="field">
<label>Author (select author): </label>
<select onChange={handleChange} name="authorId">
{displayAuthors()}
</select>
</div>
<button type="submit">+</button>
</form>
The roadblock I've hit is ensuring the dropdown menu <select> element is REQUIRED, so it ensures selecting an author is required before submitting the book info. Now, after searching through forums I've concluded that React doesn't seem to have a native solution implemented for <select> REQUIRED validation. Instead there are convoluted hack solutions as workarounds.
To mitigate the issue, I simply removed the first <option> Select author </option> (you can't see it in the code shared because I've already removed it). So that leaves the dropdown menu, by default, set on the first author in the list, on the initial render of the page. Thereby forcing the user to have a choice of author selected by default. Of course they can always change the option to choose a different author. But the point is an author choice is already enforced by default.
Now the next issue I faced with this approach is - on initial render of the page, even though an author is already selected in the dropdown list by default, the corresponding <option> element for that author, its value of author.id doesn't get detected by React on initial render of the Component (this authorId value from the option element is needed for the book submission, calling an API to submit the book info to the database).
You would have to change the menu option first for the onChange attribute event listener in <select> element to detect the value of the selected <option> (the authorId). Which means my solution for ensuring an author is always selected (even on initial page render) is now pointless as React doesn't pick up the authorID value of the initial selected author.
<option key={author.id} value={author.id}>{author.name}</option> // value={author.id} doesn't get detected in initial render of page, unless menu option is changed for `onChange` to detect value={authorId}
.
To solve this, my solution is to create a state that would be set to the first author from the list of authors fetched from the API call. So that should be Patrick Rothfuss - the default selected author in the dropdown menu. So the authorId of Patrick Rothfuss is set to this state. Then this state can be used in the book submission, if the user doesn't changed the dropdown menu at all when submitting the form.
const [firstAuthorId, setFirstAuthorId] = useState("");
function displayAuthors() {
if (loading) return (<option disabled>'Loading authors...'</option>);
if (error) return (`Error: ${error.message}`);
return data.authors.map((author, index) => {
if (index === 0) {
setFirstAuthorId(author.id);
}
return (
<option key={author.id} value={author.id}>{author.name}</option>
);
});
}
Now the issue I'm facing here is when I set the state to the first author Id, I get the error below:
In fact, from troubleshooting the issue, the cause of the issue seems to be with that particular state setter setFirstAuthorId. No matter what I set it to, and from where I set it in the Component, it throws the error shown in the screenshot.
setFirstAuthorId("anything") // will throw an error
So as a workaround (which is not ideal), I created a conditional (ternary expression) that would check, upon the book submission (where the bookEntry object state is submitted to the API), if there is no authorId, then set the authorId property of bookEntry state to a hardcoded authorId of the first author (Patrick Rothfuss). Ideally the state firstAuthorId should be set to this instead of a hardcoded ID.
function submitBook(event) {
const filledAuthorId = bookEntry.authorId? bookEntry.authorId : "5ed44e015ecb7c42a0bf824d";
addBook({ variables: {
name: bookEntry.name,
genre: bookEntry.genre,
authorId: filledAuthorId
},
refetchQueries: [{ query: GET_ALL_BOOKS_QUERY }]
})
Full code:
import React, { useState } from 'react';
import { useQuery, useMutation } from '#apollo/react-hooks';
import { GET_ALL_AUTHORS_QUERY, ADD_BOOK_MUTATION, GET_ALL_BOOKS_QUERY } from '../queries/queries';
function AddBook() {
const { loading, error, data } = useQuery(GET_ALL_AUTHORS_QUERY);
// to call more than one query using the useQuery, call useQuery hook but give alias names to loading, error, data to avoid queries overwriting each other's returned fields
// const { loading: loadingAddBook, error: errorAddBook, data: dataAddBook} = useQuery(A_SECOND_QUERY)
const [ addBook ] = useMutation(ADD_BOOK_MUTATION);
const [bookEntry, setBookEntry] = useState({
name: "",
genre: "",
authorId: ""
});
const [firstAuthorId, setFirstAuthorId] = useState("");
function handleChange(event) {
const { name, value } = event.target;
console.log(`handleChange event: ${event.target.value}`);
setBookEntry(preValue => {
return {
...preValue,
[name]: value
}
});
}
function submitBook(event) {
const filledAuthorId = bookEntry.authorId? bookEntry.authorId : "5ed44e015ecb7c42a0bf824d";
addBook({ variables: {
name: bookEntry.name,
genre: bookEntry.genre,
authorId: filledAuthorId
},
refetchQueries: [{ query: GET_ALL_BOOKS_QUERY }]
})
.then(data => {
console.log(`Mutation executed: ${JSON.stringify(data)}`);
setBookEntry(prevValue => ({
...prevValue,
name: "",
genre: ""
}));
})
.catch(err => console.log(err));
event.preventDefault();
}
function displayAuthors() {
if (loading) return (<option disabled>'Loading authors...'</option>);
if (error) return (`Error: ${error.message}`);
return data.authors.map((author, index) => {
if (index === 0) {
setFirstAuthorId(author.id);
}
return (
<option key={author.id} value={author.id}>{author.name}</option>
);
});
}
return (
<form id="add-book" onSubmit={submitBook}>
<h1>Add a new book to Wiki Books</h1>
<div className="field">
<label>Book name: </label>
<input type="text" onChange={handleChange} name="name" required value={bookEntry.name}/>
</div>
<div className="field">
<label>Genre: </label>
<input type="text" onChange={handleChange} name="genre" required value={bookEntry.genre}/>
</div>
<div className="field">
<label>Author (select author): </label>
<select onChange={handleChange} name="authorId">
{displayAuthors()}
</select>
</div>
<button type="submit">+</button>
</form>
);
}
export default AddBook;
Given that you only need to update the firstAuthorId state with the first index of the data.authors array that is returned from the query, you should do it when the component is mounted, rather than calling the displayAuthors method and updating the state everytime to component re-renders.
This can be achieved using the useEffect hook.
useEffect(() => {
setFirstAuthorId(data.authors[0].id)
}, []);
Alternatively, you can set data.authors as part of the dependency array, such that the firstAuthorId state will be updated every time data.authors has changed.
useEffect(() => {
setFirstAuthorId(data.authors[0].id)
}, [data.authors]);
Better still, there is actually no need to maintain a separate state(firstAuthorId) to track the first object of data.authors. You can simply reference to it on whereever you need in the code itself.

Why is my POST request causing a complete rerender?

I'm new to react and having some very curious behaviour when sending a post request to firebase, hoping someone can explain.
I have a form with two required text areas, both set respective state keys to their input value onChange.
I then have a button hooked to a POST function using an axios instance.
The request goes through absolutely fine if none, or only one of the text areas has input.
If both text areas have input the request doesn't happen and instead I get a complete re-render/refresh of the page.
I have tried chopping the code up many ways, adding conditionals to the POST function changing/testing the instance to see if I'm missing something, but nothing seems to work. I can't understand what is causing the rerender and stopping the request when the only change is a second input value. Any help appreciated!
Here is my code:
class NewPostForm extends Component {
state = {
title: "",
offer: "",
return: "",
};
postHandler = () => {
const post = {
title: this.state.title,
offer: this.state.offer,
return: this.state.return,
};
instance
.post("/posts.json/", post)
.then((response) => {
console.log(response);
})
.catch((error) => {
console.log(error);
});
}
};
render() {
return (
<form>
<textarea
key={"in0"}
value={this.state.offer}
onChange={(event) => this.setState({ offer: event.target.value })}
required
/>
<textarea
key={"in1"}
value={this.state.return}
onChange={(event) => this.setState({ return: event.target.value })}
required
/>
<div style={{ display: "flex", alignItems: "center" }}>
<MainButton click={this.postHandler} label={"Post"} />
<MainButton click={this.props.click} label={"Cancel"} />
</div>
</form>)
MainButton def:
const MainButton = (props) => {
return (
<button onClick={props.click} style={style}> {props.label} </button>
);
};
A button or submit input within a form can trigger the form being "submitted" to a target, which in classic HTML fashion means sending the inputs back to the server, and then loading what the server returns into a new document. If you're doing fetch/axios/XHR/whatever instead on form button clicks, you need to prevent the default behavior through the event object. Change your onClick handler to:
postHandler = e => {
e.preventDefault();
// rest of the code
};

Get value from an input in an array on button click ReactJs

I am relatively new to ReactJs.I am learning react while I am trying to create a real world app. Here is something I cannot solve.
I have a repeated component that has one input and one button.
everytime the button is clicked, the value of the input will be used in one function.
In Angular I do not have to worry about how to passing those value since in ngFor we can directly assign the value from the ngModel. But there is no such concept in React.
betOnTeam = (_id, coins) => {
return;
};
{this.teamList.map(team => (
<div key={team._id}>
<input type="number" min="100" max="5000" />
<button type="button"
onClick={() => this.betOnTeam(team._id,//value from the
input above)}>
</div>
))}
So basically I Have a function ready to receive an Id and how many coins the user bet.
And As we can see from the picture, I have many inputs which should contain the value of how much coins the user put for a specific team.
each button will trigger this betOnTeam function and will pass the unique Id of the team, and the number coins the user bet.
How can I set states for all thoese teams since they are all dynamic, it could be 5 teams or 100 teams. Is it any way to do it dynamically?
e.g. user input 5000, when he click the button, the id and the value will be passed into the function betOnTeam.
I hope this clarified my question.
==================================
Thanks for all the input from you guys.
I have make it working combine with all your suggestions.
So Here is what I do:
betOnTeam = (event, id) => {
console.log(event.target[0].value, id);
return;
};
{this.teamList.map(team => (
<form key={team._id} onSubmit={(e) => this.betOnTeam(e,team._id)}>
<input type="number" min="100" max="5000" />
<button type="submit">
</form >
))}
Seems like you're really close. I think this ultimately comes down to how you want to construct your components. There is an easy way to do this (the more React) way, and there is a hard way.
The easy way is to split the mark-up created inside the .map() into its own component. You will have an individual component for each team, thus the state is encapsulated to its own component. By doing this you can effectively keep track of the inputs for each team.
Consider this sandbox for example: https://codesandbox.io/s/dazzling-roentgen-jp8zm
We can create a component for the markup like this:
Team
import React from "react"
class Team extends React.Component {
state = {
betValue: 100
};
handleOnChange = e => {
this.setState({
betValue: e.target.value
});
};
handleOnClick = () => {
this.props.betOnTeam(this.state.betValue, this.props.id);
};
render() {
const { id } = this.props;
const { betValue } = this.state;
return (
<div key={id}>
<input
type="number"
min="100"
max="5000"
value={betValue}
onChange={this.handleOnChange}
/>
<button onClick={this.handleOnClick} type="button">
Bet
</button>
</div>
);
}
}
export default Team;
So from a purely jsx standpoint, the markup is the same, but now it is contained inside a class-component.
Now we can keep track of the inputs in a controlled manner.
When we're ready to place the bet, the value is stored in the
individual component state.
We pass down two properties to each Team component, the team_id and
betOnTeam function. The team_id can be accessed using this.props.id and likewise we will pass it into this.props.betOnTeam() when required.
Main Component
import React from "react"
import Team from "./Team"
class App extends React.Component {
teamList = [
{ team_id: 1, name: "TSM" },
{ team_id: 2, name: "SKT" },
{ team_id: 3, name: "CLG" }
];
betOnTeam = (betValue, teamId) => {
console.log(betValue);
console.log(teamId);
};
render() {
return (
<div>
{this.teamList.map(team => (
<Team id={team.team_id} betOnTeam={this.betOnTeam} />
))}
</div>
);
}
}
So the .map() renders a Team component for each team and passes in their respective ids and the betOnTeam function as props. When the button inside the component is clicked, we can pass back up the values stored in the Team Component to execute betOnTeam.
onClick={this.betOnTeam(form._id,value)}
Don't execute this.betOnTeam right from the start, you're actually setting the click handler to the returned result of this.betOnTeam(form._id,value). In React, it's not quite the same as in Angular. For this, you need to set it equal to a function that does that. Like this:
onClick={() => this.betOnTeam(form._id,value)}
Hope this helps.
1. this.betOnTeam = (_id, value) => { ... }
2. constructor(props) { this.betOnTeam.bind(this) }
3. onClick = { () => this.betOnTeam(form._id, value)}
Well if you use onClick = { this.betOnTeam(form._id, value) }, then the code will be executed first, and in betOnTeam function, you will not use 'this' operator.
But if you use the above methods, then you can use 'this' in the function and get the good results.
And your code has some bugs to fix,
{this.array.map(form => (
<div key={form._id}>
<input name={`editform{form._id}`} type="number" min="100" max="5000" onChange={(e) => this.changeNumber(e, form._id) }/>
<button type="button"
onClick={this.betOnTeam(form._id,value)}>
</div>
))}
And in changeNumber function, you should use setState({}) function to set the value to the state, and in betOnTeam function, you can use the state you have already set.
The code must be like this, or otherwise you can use ref but it is not formally encouraged to use ref.
Totally, you should use ControlledComponent. That's the target.
I hope you to solve the problem.

Categories