I have a json file that is an array that stores another field that contains another array that I want. I have an ajax request that stores the pitching field into a state array pitchers I have two buttons that when clicked will pass a value equal to the team_flag attribute in the json file.
<button className="btn" onClick={this.handleClick.bind(this, 'home')}>{homeTeamName}</button>
<button className="btn" onClick={this.handleClick.bind(this, 'away')}>{awayTeamName}</button>
And the method:
handleClick: function(teamFlag) {
// setState of pitchers to whichever team is clicked (home, away)
// this.setState({ pitchers: this.state.pitchers.teamFlag})???
console.log(teamFlag);
}
How do I set the pitchers state so that it will take the pitcher array that corresponds to the team_flag that was clicked? (i.e. If I click on homeTeam it will store the pitcher array that is under the "team_flag": "home") Below is the json file
"pitching":[
{
"pitcher":[
{"name": "Billy", "hand": "right"}
],
"team_flag":"away",
},
{
"pitcher":[
{"name": "Joe", "hand": "right"}
],
"team_flag":"home",
}
],
Assuming you have the entire "pitching" array in the state under the pitching key you could something like:
var pitchers = this.state.pitching.find(function(team) {
return team.team_flag === teamFlag;
});
this.setState({ pitchers: pitchers.pitcher });
If it is only for presentation purposes I wouldn't save the result in the state, instead I'd save the teamFlag and call the mentioned code in the render method.
React is smart enough to know that if teamFlag or the pitching array changes then it needs to re-render.
EDIT:
class YourComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: true
}
}
componentWillMount() {
// make your ajax request
// and when the request is finished
this.setState({ loading: true });
}
handleClick(ev, flag) {
this.setState({ flag: flag });
}
render() {
let homeTeamName = 'Home';
let awayTeamName = 'Away';
let selectedFlag = this.state.flag;
let pitchers = this.state.pitching.find(function(team) {
return team.team_flag === selectedFlag;
});
return (
<div>
{!this.state.loading ?
<div>
<button className="btn" onClick={this.handleClick.bind(this, 'home')}>{homeTeamName}</button>
<button className="btn" onClick={this.handleClick.bind(this, 'away')}>{awayTeamName}</button>
</div>:
null
}
// render using the pitchers array
</div>
);
}
};
Related
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>
);
}
}
I am trying to create a list of subcategories that I'm getting from the API and display them in the App. The problem is that I don't know how to transform the items from the Array (API) into List items.
componentDidMount(){
axios.get('/categories/' + this.props.match.params.id)
.then(response => {
console.log(response.data.children) //Array of strings
})
}
render(){
return(
<div className={classes.Showcategory}>
<h1>{this.props.match.params.id}</h1>
<li>Here I need for each string of the array a list item<li/>
</div>
);
}
You can define a state variable for the component. When you are making a request, update the state. When state gets update your component will be re rendered, with the data you want.
Try this:
constructor(props) {
super(props);
this.state = {
categories: []
};
}
componentDidMount() {
axios.get("/categories/" + this.props.match.params.id).then(response => {
console.log(response.data.children); //Array of strings
this.setState({ categories: response.data.children });
});
}
render() {
return (
<div className={classes.Showcategory}>
<h1>{this.props.match.params.id}</h1>
{this.state.categories.map((category, index) => (
<li key={index}>{category}</li>
))}
</div>
);
}
Note that React recommends that you don't use index as the key. For your case, if category strings are unique, use those instead.
I am trying to build a ToDoList app and I have two components. I have a main component that handles the state and another button component that renders a delete button next to every task that I render. The problem I have is that i cant seem to connect the delete button to the index of the array and delete that specific item in the array by clicking on the button next to it.
I have tried to connect the index by using the map key id to the delete function.
just need help with how my delete function should look like and how its going to get the index of the item that is next to it and delete it.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
userInput: '',
toDoList : []
}
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChange = this.handleChange.bind(this);
this.delete = this.delete.bind(this);
}
handleSubmit() {
const itemsArray = this.state.userInput.split(',');
this.setState({
toDoList: this.state.toDoList.concat(itemsArray),
userInput: ''
});
}
handleChange(e) {
this.setState({
userInput: e.target.value
});
}
delete(id) {
this.setState({
toDoList: this.state.toDoList.filter( (item) => id !== item.id )
})
}
render() {
return (
<div>
<textarea
onChange={this.handleChange}
value={this.state.userInput}
placeholder="Separate Items With Commas" /><br />
<button onClick={this.handleSubmit}>Create List</button>
<h1>My Daily To Do List:</h1>
<Button toDoList={this.state.toDoList} handleDelete={this.delete} />
</div>
);
}
};
class Button extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<ul>
{
this.props.toDoList.map( (item) => <li key={item.id}>{item.text} <button onClick={this.props.delete(item.id)}>Done!</button></li> )
}
</ul>
);
}
};
I reviewed your edited code and made a couple of changes.
I don’t get what exactly you want to achieve with you handleSubmit method but items it adds to the list are simple strings and don’t have neither ‘id’ nor ‘text’ properties you’re referring to in other places. Possibly you’re going to change this later but while your to do items are just strings I’ve edited your code so that it work properly under this condition.
Edited delete method now accepts not item.id as a parameter but the whole item object. Yet I'm using functional form of setState as it was correctly suggested by #Hamoghamdi
delete(itemToDelete) {
this.setState(state => ({
toDoList: state.toDoList.filter( (item) => itemToDelete !== item)
}))
}
Edited render method of Button class now displays items as text and properly bind delete handler...
render() {
return (
<ul>
{
this.props.toDoList.map( (item) => <li key={item}>
{item}
<button onClick={() => this.props.handleDelete(item)}>Done!</button>
</li> )
}
</ul>
);
}
BTW Button is a bad naming for the component that isn’t exactly a button. Yet it’s better to implement it as a functional component. Use class components only if the component has its own state.
you should try using an anonymous function with setState() instead of returning an object literal directly, specially when you want to do something affected by the previous or current state
using this.state inside of setState() won't give you any good results.
here, try this:
delete = (id) => {
this.setState((prevState) => {
return { toDoList: prevState.filter( (task) => id !== task.id )}
});
You need to bind the method in constructor for example:
constructor(props) {
//...
this.handleDelete = this.handleDelete.bind(this)
}
also you can find another ways how to bind methods
In terms of handling the deleting the items, you can use
handleDelete(index) {
// Use the splice array function: splice(index, deleteCount)
this.todoList.splice(index, 1);
}
And that is all that easy
So I have an array is a class's state. Let's call it A. A is populated with objects of type B through a function f in the constructor. Later, I generate using f and new data, a new array of objects of type B called C. I then use setState({A: C}). However, this results in the data from the first array staying on the display. I'm not sure how to fix this at all.
Edit: code snippets
class ClassBox extends Component {
constructor(props) {
super(props);
// note data here uses the keys from the config file
this.state = {
data: this.props.data,
filteredData: [],
functionData: []
};
this.generateFunctionData = this.generateFunctionData.bind(this);
this.state.functionData = this.generateFunctionData();
this.state.filteredData = this.state.functionData;
this.handleSearch = this.handleSearch.bind(this);
}
generateFunctionData(useData = false, data = null){
return useData ? ProcessJSON.extractFunctions(data.functions).map((_function, index) =>
{return createMethodBox(_function.Func_name, _function.Description, _function.Access_Mod, index)}) : ProcessJSON.extractFunctions(this.props.data.functions).map((_function, index) =>
{return createMethodBox(_function.Func_name, _function.Description, _function.Access_Mod, index)});
}
handleSearch(input) {
// convert to lower case to avoid capitalization issues
const inputLowerCase = input.toString().toLowerCase();
// filter the list of files based on the input
const matchingList = this.state.functionData.filter((method) => {
return method.props.name.toLowerCase().includes(inputLowerCase)
}
);
this.setState({
filteredData: matchingList
});
}
render() {
console.log(this.state.filteredData)
return (
<Container>
<NameContainer>
<h1>{this.state.data.className}</h1>
</NameContainer>
<ContentContainer>
<DescriptionContainer>
{this.state.data.description}
</DescriptionContainer>
<StyledDivider/>
<VarContainer>
<h1>Variables</h1>
<VarTableContainer>
<BootstrapTable
keyField="id"
data={[]}
columns={testColumns}
bordered={false}
noDataIndication="Table is Empty"
classes="var-table"
/>
</VarTableContainer>
{/*{this.state.data.variables}*/}
</VarContainer>
<StyledDivider/>
<MethodContainer>
<MethodHeader>
<h1>Methods</h1>
<StyledSearchBar onSearch={this.handleSearch}
isDynamic={true} allowEmptySearch={false} minChars={0}
className='searchBar'/>
</MethodHeader>
<Methods>
{this.state.filteredData}
</Methods>
</MethodContainer>
</ContentContainer>
</Container>
);
}
class Classes extends Component {
constructor(props) {
super(props);
this.state = {
data: this.props.data,
displayIndex: this.props.displayIndex
};
this.treeData = createTreeData(this.state.data);
this.classBox = null
}
componentDidUpdate(prevProps, prevState, snapshot) {
if(prevState.displayIndex !== this.props.displayIndex){
const funcData = this.classBox.generateFunctionData(true, this.state.data[0][this.props.displayIndex]);
console.log(funcData);
this.classBox.setState({data: this.state.data[0][this.props.displayIndex], functionData: funcData, filteredData: funcData });
this.classBox.forceUpdate();
this.setState({displayIndex: this.props.displayIndex});
}
}
render() {
this.treeData = createTreeData(this.state.data);
return (
<Container>
<FileTreeContainer>
<StyledTreeMenu data={treeData}/>
</FileTreeContainer>
<ClassInfoContainer>
<ClassBox ref = {el => this.classBox = el} data = {this.state.data[0][this.state.displayIndex]}/>
</ClassInfoContainer>
</Container>
)
}
Classes contains an instance of ClassBox. After executing componentDidUpdate, the page still shows the old method boxes, even though functionData has changed.
EDIT 2: it's also worth noting that when I replace the class view with the landing view and go back to the class view it shows the page correctly.
The way your are setting the state should be correct, as you are setting it to a newly created array from .filter.
I think the issue is with you storing the method components in the filteredData state. Components should not be stored in state.
I believe your component is just re-rendering, but not removing the old generated components. Maybe try mapping the search input to the state and generate the method components that way.
I have three files: ShopsContainer.js ShopsComponent.js and ShopsItemComponent.js
ShopsContainer maintains an array of shop items in local state that gets passed down into ShopsComponent as props. ShopsComponent then maps through the items array that is being received as props and renders a ShopsItemComponent for each item in the array.
Within my ShopsContainer file, I have a method that removes a shop item from state using the following code:
removeShop = (shopAccount) => {
this.setState(prevState => ({
items: prevState.items.filter(shop => {
return shop.shopAccount !== shopAccount
})
}));
}
When this happens, the correct item is removed from the items array in state, however, whatever the last ShopItem is that is in the DOM at the time of the removeShop call will get removed no matter if it is the correct item that should be removed or not. In other words, when removeShop gets called and the items array in state gets updated correctly, the wrong ShopItemComponent gets removed from the DOM.
What I would like to happen (or what I think should happen) is when removeShop gets called, that shop gets removed from the items array in state and ShopsContainer re-renders causing ShopsComponent to re-render with the updated props being received. And lastly ShopsComponent would map through the newly updated items array in props displaying a `ShopItemComponent for the correct items. Perhaps the problem has to do with the props being updated?
My code is as follows:
ShopsContainer.js
class ShopsContainer extends Component {
constructor() {
this.state = {
items: null
}
this.getAll();
this.removeShop = this.removeShop.bind(this);
}
getAll = () => {
// API request that fetches items and updates state
}
removeShop = (shopAccount) => {
this.setState(prevState => ({
items: prevState.items.filter(shop => {
return shop.shopAccount !== shopAccount
})
}));
}
render() {
return (
<div>
{this.state.items ? <ShopComponent items={this.state.items} removeShop={this.removeShop} /> : <div><h1>Loading...</h1></div>}
</div>
);
}
}
ShopsComponent.js
class ShopsComponent extends Component {
constructor() {
this.handleRemove = this.handleRemove.bind(this);
}
handleRemove = (shopAccount) => {
this.props.removeShop(shopAccount);
}
render() {
return (
<React.Fragment>
<Header />
{this.props.items.map((shopItem, i) => {
return (<ShopItemComponent key={i} item={shopItem} removeShop={this.handleRemove} />);
})}
</React.Fragment>
);
}
}
Your code is working great, but you only has one mistake , your ShopComponent is assign index as a key for each ShopItemComponent and react is tracking those indexes to update the correct component, so you need to set key as a unique value between items, then I realize that shopAccount should be your id for each item.
The solution code is below.
class ShopsComponent extends Component {
handleRemove = (shopAccount) => {
this.props.removeShop(shopAccount);
}
render() {
return (
<React.Fragment>
<Header />
{this.props.items.map((shopItem) => <ShopItemComponent key={shopItem.shopAccount} item={shopItem} removeShop={this.handleRemove} />)}
</React.Fragment>
);
}
}
I hope you can find useful.
Note, when you are using a arrow function into your class, don't bind that method into the constructor, so remove it, because
handleRemove = (shopAccount) => {
this.props.removeShop(shopAccount);
}
is already binded.