I'm new to React, and despite many attempts I can't figure out how to set my id dynamically in axios requests in my edit-form.
I tried to set the axios requests with a static number just to test my edit-form, I can log my object but when I submit it deletes the product data instead of updating with the new data because the id is undefined.
I think I just need to find out what to write in my componentDidMount and handleSubmit to catch the id.
For example ${this.state.product.id} is undefined, why isn't it working?
It's my first question, so I hope I am clear and tell me if you need more code.
EditForm
import React, { Component } from 'react';
import { withRouter } from 'react-router';
import axios from 'axios';
import Navbar from 'react-bootstrap/Navbar';
import Nav from 'react-bootstrap/Nav';
import Form from 'react-bootstrap/Form';
import Col from 'react-bootstrap/Col';
import FormControl from 'react-bootstrap/FormControl';
import Button from 'react-bootstrap/Button';
class EditForm extends React.Component {
constructor(props) {
super(props);
this.state = { product: [] };
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
};
componentDidMount = () => {
axios
.get(`/products/edit-form/${this.props.match.params.id}`)
.then(response => {
console.log(response.data.products);
this.setState({
product: response.data.products
})
});
};
handleChange(e) {
this.setState({id: e.target.value})
this.setState({reference: e.target.value})
this.setState({designation: e.target.value})
}
handleSubmit(e) {
e.preventDefault();
const data = {
id: this.state.product.id,
reference: this.state.product.reference,
designation: this.state.product.designation
};
axios
.post(`/products/${this.props.match.params.id}`, data )
.then(res => console.log(res))
.catch(err => console.log(err));
};
renderForm() {
return this.state.product.map((product, index) => {
const { id,reference,designation } = product
return(
<>
<Form className="post" onSubmit={this.handleSubmit}>
<Form.Row>
<Form.Group as={Col} controlId="formGridReference">
<Form.Label>Reference</Form.Label>
<Form.Control type="text" value={this.state.product.reference}
onChange={this.handleChange} name="reference" placeholder={reference} />
</Form.Group>
<Form.Group as={Col} controlId="formGridDesignation">
<Form.Label>Designation</Form.Label>
<Form.Control type="text" value={this.state.product.designation}
onChange={this.handleChange} name="designation" placeholder={designation} />
</Form.Group>
</Form.Row>
<Button variant="primary" type="submit">
Submit
</Button>
</Form>
</>
);
})
}
render() {
return (
<div>
<h1>Formulaire de modification</h1>
{this.renderForm()}
</div>
);
}
}
export default withRouter(EditForm);
App.js
class App extends Component {
render() {
return (
<React.Fragment>
<NavBar />
<Router>
<Route exact path="/" component={Products}/>
<Route path="/edit-form/:productId" component={EditForm}/>
</Router>
</React.Fragment>
);
}
}
Products
import React, { Component } from 'react';
import Table from 'react-bootstrap/Table';
import Button from 'react-bootstrap/Button';
import { Link } from 'react-router-dom';
import axios from 'axios';
const headings = [
'id','reference','designation'
];
export default class Products extends Component {
constructor(props) {
super(props);
this.state = {
products: []
};
};
componentDidMount = () => {
axios.get("/products").then(response => {
console.log(response.data.products);
this.setState({
products: response.data.products
})
});
};
renderTableHeader() {
return headings.map((key, index) => {
return <th key={index}>{key.toUpperCase()}</th>
})
}
renderProductData() {
return this.state.products.map((product, index) => {
const { id,reference,designation } = product
return (
<tr key={id}>
<td>
{id}
<Link to={`/edit-form/${id}`}>Modifier</Link>
</td>
<td>{reference}</td>
<td>{designation}</td>
</tr>
)
})
}
render() {
return (
<div>
<h1 id='title'>Produits</h1>
<Table striped bordered hover id='products'>
<thead>
{this.renderTableHeader()}
</thead>
<tbody>
{this.renderProductData()}
</tbody>
</Table>
</div>
);
}
}
Async functions
// productController.js
const getProduct = async (req, res) =>
{
const { productId } = req.params;
const product = await productDb.getProduct(productId);
res.status(200).send({ products: product.rows });
};
const postProduct = async (req, res) =>
{
const { productId } = req.params;
const { reference,designation } = req.body;
await productDb.updateProduct(productId, reference, designation);
res.status(200).send(`Le produit reference ${reference} a été modifié`);
console.log(`Le produit reference ${reference} a été modifié`);
// productDb.js
const getProduct = async (id) =>
{
const connection = new DbConnection();
return await connection.performQuery(`SELECT * FROM produits WHERE id=${id}`);
};
const updateProduct = async (id, reference, designation) =>
{
const connection = new DbConnection();
await connection.performQuery("UPDATE produits SET reference=?, designation=? WHERE id=?",
[reference, designation, id]);
};
Thank you
In your Products.js, you are using links to open up your EditForm component. So, for you to be able to access the product id, you need to wrap your EditForm component with the withRouter hoc from the react-router library. This hoc will then make the match prop available to your EditForm component.
So, you need to add react-router to your dependencies by running this:
yarn add react-router **OR** npm install react-router
Then add it to your import statements in EditForm.js
import { withRouter } from 'react-router';
And then inside componentDidMount, you should be able to access the id by doing this:
this.props.match.params.id
You also need to change your class definition to:
class EditForm extends React.Component
And then export it like this:
export default withRouter(EditForm)
I assume in your routes file, you must have a route that is defined like this:
<Route path="/edit-form/:id" component={EditForm} />
Related
I have a web app that is suppose to show a list of notes made by the user on the dashboard if said list exist (that is if the user wrote any note at all). I wrote the reducer, the actions and I connected state and dispatch in order for it to work. But for some reason the notes created don't appear once in the dashboard when I write them, I already made sure that the ADD_NOTE action gets fired and that the reducer updates the data in redux, but in the dashboard component that data disappears.
This is my reducer.
export default (state = [], action) => {
switch (action.type) {
case "ADD_NOTE":
return [
...state,
action.note
];
case "REMOVE_NOTE":
return state.filter(({ id }) => id !== action.id);
default:
return state;
}
}
And those are my actions
import { v4 as uuidv4 } from 'uuid';
export const addNote = ({ title = "", body = ""} = {}) => ({
type: "ADD_NOTE",
note : {
title,
body,
id : uuidv4()
}
});
export const removeNote = ({ id } = {}) => ({
type: "REMOVE_NOTE",
id
});
This is the component that holds the create note form.
import React, { Component } from 'react';
class CreateNote extends React.Component{
constructor(props){
super(props);
this.onTitleChange = this.onTitleChange.bind(this);
this.onBodyChange = this.onBodyChange.bind(this);
this.onSubmit = this.onSubmit.bind(this);
this.state = {
title: "",
body: "",
error: ""
}
}
onTitleChange(e){
const title = e.target.value;
this.setState({ title });
}
onBodyChange(e){
const body = e.target.value;
this.setState({ body });
}
onSubmit(e){
e.preventDefault();
if(!this.state.title || !this.state.body){
this.setState({ error : "Please fill in all gaps"});
} else {
this.setState({ error: ""});
const data = { title: this.state.title, body: this.state.body}
this.props.onChange(data);
}
}
render(){
return(
<div>
{this.state.error && <p>{this.state.error}</p>}
<form onSubmit = {this.onSubmit}>
<label>Put a title for your note</label>
<input
placeholder="Title"
type="text"
value={this.state.title}
autoFocus
onChange = {this.onTitleChange}
/>
<label>Write your note</label>
<textarea
placeholder="Note"
value={this.state.body}
autoFocus
onChange = {this.onBodyChange}
/>
<input type="submit" value="Submit"/>
</form>
</div>
);
}
}
export default CreateNote;
And this is the component that fires the ADD_NOTE action
import React, { Component } from 'react';
import CreateNote from "./actions/CreateNote";
import Header from "./Header";
import { addNote } from "../actions/noteActions"
import { connect } from 'react-redux';
class Create extends React.Component{
constructor(props){
super(props);
this.eventHandler = this.eventHandler.bind(this);
}
eventHandler(data){
this.props.addNote(data);
this.props.history.push("/");
}
render(){
return (
<div>
<Header />
<CreateNote onChange = {this.eventHandler}/>
</div>
)
}
}
const mapDispatchToProps = (dispatch) => ({
addNote: (note) => dispatch(addNote(note))
});
export default connect(null, mapDispatchToProps)(Create);
And finally this is the dashboard component that renders the notes if they exist
import React from "react";
import ListItem from "./actions/ListItem";
import { connect } from 'react-redux';
const ListGroup = (props) => (
<div>
{
props.notes.length === 0 ? <h1>Write a note!</h1> :
(
props.notes.map((note) => {
return <ListItem key={note.id} {...note} />;
})
)
}
</div>
)
// The mapStateToProps does not connect with the local state, the action ADD_NOTE fires whenever
// the Create form is submited and the reducer updates the redux storage. So the problem lies here ?
// It could be that state.note is not definded but I don't know where should I define it if I have to,
// and apparently I don't have to ???????????????
const mapStateToProps = (state) => {
return {
notes: state.note
};
};
export default connect(mapStateToProps)(ListGroup);
When I try to run this it fires an error:
ListGroup.js?11a1:5 Uncaught TypeError: Cannot read property 'length' of undefined
at ListGroup (ListGroup.js?11a1:5)
Showing that the data that gets passed to the props is undefined. I'm thinking that it could be that state.note is not defined and I have to define it somewhere but I don't know if that's the case.
Use Hooks in functional components
connect() is only valid for class based components. For functional components you need to use hooks. Specifically the useSelector hook for reading redux state and useReducer to emit actions. You can find more instructions on redux hooks here https://react-redux.js.org/api/hooks#useselector
SearchBox.js File is:
import React from 'react';
const SearchBox = ({ searchfield, searchChanger}) => {
return (
<div className='pa2'>
<input
className='pa3 b--green bg-light-blue'
type='search'
placeholder='search robots'
onChange={searchChange}
/>
</div>
);
}
export default SearchBox;
App.js File is:
import React, { Component } from 'react';
import CardList from './CardList';
import SearchBox from './SearchBox';
import { robots } from './robots';
import './App.css';
class App extends Component {
constructor() {
super()
this.state = {
robots: robots,
searchfield:''
}
}
componentDidMount() {
fetch('https://jsonplaceholder.typicode.com/users')
.then(response => response.json())
.then(users => {this.setState({ robots: robots})});
}
onSearchChange = (event) => {
this.setState({ searchfield: event.target.value })
}
render() {
const filteredRobots = this.state.robots.filter(robots => {
return robots.name.toLowerCase().includes(this.state.searchfield.toLowerCase());
})
if (robots.length ===0) {
return <h1>Loading</h1>
}
else {
return (
<div className='tc'>
<h1 className='f1'>RoboFriends</h1>
<SearchBox searchChange={this.onSearchChange}/>
<CardList robots={filteredRobots} />
</div>
);
}
}
}
export default App;
I was able to get rid of this error just by changing the order of import in App.js file, but later on its showing this error no matter what i do?
please help in case I have any typing issue, if no then what is the problem
Your SearchBox should be:
const SearchBox = ({ searchfield, searchChange}) => {
return (
<div className='pa2'>
<input
className='pa3 b--green bg-light-blue'
type='search'
placeholder='search robots'
onChange={searchChange}
/>
</div>
);
}
export default SearchBox;
In your props there was searchChanger which is incorrect.
I was moving actions from the sign in component to the context (I configured context API) everything was going fine until i had to install graphql and moved the mutations files from the sign in component to the context file. Right now is giving me the following error:
Argument of undefined passed to parser was not a valid GraphQL DocumentNode. You may need to use 'graphql-tag' or another method to convert your operation into a document
this error has came out when i used when i call the mutation at the bottom of my AppProvider component. i'm thinking that is not that way with context to use graphql mutations.
AppProvider:
// React
import React, { Fragment } from 'react';
// React apollo
import { graphql } from 'react-apollo';
import * as compose from 'lodash.flowright';
// React router
import { withRouter, Redirect } from 'react-router-dom';
// Mutation
import mutations from './mutations';
// Context
export const Context = React.createContext();
class AppProvider extends React.Component {
constructor(props) {
super(props);
this.state = {
// Login & logout states
login_credentials: {},
isLogged: false,
get_data: this.get_data,
submit: this.submit,
}
}
// Actions
// Login & Logout
get_data = async(e) => {
e.preventDefault();
const { name, value } = e.target;
const data = { [name]: value };
const newData = { ...this.state.login_credentials, ...data };
this.setState({
login_credentials: newData
});
}
submit = async(e) => {
e.preventDefault();
const { signinUser } = this.props;
const { login_credentials, isLogged } = this.state;
try {
let variables = login_credentials;
const response = await signinUser({variables});
const get_token = response.data.signinUser.token;
// setting localStorage
localStorage.setItem('token', get_token);
this.setState({
isLogged: true
});
this.props.history.push({pathname: '/home', state: {isLogged: this.state.isLogged}});
} catch(error) {
console.log(error);
}
}
// END LOGIN & LOGOUT ACTIONS
render() {
return(
<Context.Provider value={this.state}>
{this.props.children}
</Context.Provider>
);
}
}
export default compose(
withRouter,
graphql(mutations.signinUser, { name: 'signinUser' }),
)(AppProvider)
Sign in component where i used to use the functions and mutations:
// React
import React, { Fragment } from 'react';
// React apollo
import { graphql } from 'react-apollo';
import * as compose from 'lodash.flowright';
// React router
import { withRouter, Redirect } from 'react-router-dom';
// import mutations
import mutations from './mutations';
// React bootstrap
import { Container, Row, Form, Button } from 'react-bootstrap';
// Import Style
import './style.css';
// Component
import NavbarLayout from '../Navbar';
// Context
import Context from '../../context/context';
class LoginForm extends React.Component {
/* state = {
login_credentials: {},
isLogged: false
}
get_data = async(e) => {
e.preventDefault();
const { name, value } = e.target;
const data = { [name]: value };
const newData = { ...this.state.login_credentials, ...data };
this.setState({
login_credentials: newData
});
}
submit = async(e) => {
e.preventDefault();
const { signinUser } = this.props;
const { login_credentials, isLogged } = this.state;
try {
let variables = login_credentials;
const response = await signinUser({variables});
const get_token = response.data.signinUser.token;
// setting localStorage
localStorage.setItem('token', get_token);
this.setState({
isLogged: true
});
this.props.history.push({pathname: '/home', state: {isLogged: this.state.isLogged}});
} catch(error) {
console.log(error);
}
}
*/
render() {
return(
<Fragment>
<Context.Consumer>
{c => {
return(
<Container>
<Form className="form-container">
<h2 className="text-center pb-4">Ingreso</h2>
<Form.Group controlId="formBasicEmail">
<Form.Control name='email' onChange={e => c.get_data(e)} type="email" placeholder="Email" />
</Form.Group>
<Form.Group controlId="formBasicPassword">
<Form.Control name='password' onChange={e => c.get_data(e)} type="password" placeholder="Contraseña" />
</Form.Group>
<div className="text-center">
<Button className="button-login" variant="primary" onClick={e => c.submit(e)} type="submit">
Ingresa
</Button>
</div>
</Form>
</Container>
);
}}
</Context.Consumer>
</Fragment>
);
}
}
export default compose(
/* withRouter,
graphql(mutations.signinUser, { name: 'signinUser' }), */
)(LoginForm);
So I am trying to build my first react.js app and am wondering, why my page does neither update after POSTING a new player to my app via axios, nor after DELETING one. As I refresh the page afterwards the players are created or gone depending on the operation. So the API part is working just fine.
I have a PlayerPage.js like so:
PlayerPage.js
import React, { Component } from 'react';
import axios from 'axios';
import PlayerForm from './playerform';
import PlayCard from './playercard';
class PlayerPage extends Component {
constructor(props) {
super(props);
}
state = {
players: []
};
componentDidMount() {
axios
.get('http://localhost:3001/player')
.then(res => {
const players = res.data;
this.setState({ players });
})
.catch(console.log);
}
render() {
return (
<div>
<h2>Add Player</h2>
<PlayerForm />
<hr></hr>
<h2>Available Player</h2>
{this.state.players.map(player => (
<PlayCard player={player} key={player.id} />
))}
</div>
);
}
}
export default PlayerPage;
It does reference (and fill in from API) the PlayerForm and PlayerCard component, which look like this:
playerform.js
import React, { Component } from 'react';
import axios from 'axios';
import {
FormControl,
FormGroup,
FormLabel,
Form,
Button
} from 'react-bootstrap';
class PlayerForm extends Component {
state = {
name: '',
nickname: ''
};
handleSubmit = e => {
e.preventDefault();
axios
.post('http://localhost:3001/player', JSON.stringify(this.state), {
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
}
})
.then()
.catch(err => console.log(err));
};
handelChange = e => {
this.setState({
[e.target.name]: e.target.value
});
};
render() {
return (
<Form onSubmit={this.handleSubmit}>
<FormGroup>
<FormLabel>Name</FormLabel>
<FormControl
type="text"
name="name"
placeholder="Enter player name"
onChange={this.handelChange.bind(this)}
/>
</FormGroup>
<FormGroup>
<FormLabel>Nickname</FormLabel>
<FormControl
type="text"
name="nickname"
placeholder="Enter player nickname"
onChange={this.handelChange.bind(this)}
/>
</FormGroup>
<Button variant="btn btn-primary" type="submit">
Add
</Button>
</Form>
);
}
}
export default PlayerForm;
and playercard.js:
import React, { Component } from 'react';
import { Card, Button } from 'react-bootstrap';
import PropTypes from 'prop-types';
import axios from 'axios';
class PlayerCard extends Component {
constructor(props) {
super(props);
this.player = props.player;
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
axios
.delete(`http://localhost:3001/player/${this.player.id}`)
.then()
.catch(console.log);
}
render() {
return (
<Card className="flex-row flex-wrap">
<Card.Header>
<Card.Img variant="left" src="https://via.placeholder.com/150" />
</Card.Header>
<Card.Body variant="center">
<Card.Title>{this.player.name}</Card.Title>
<Card.Subtitle>{this.player.nickname}</Card.Subtitle>
</Card.Body>
<Card.Body variant="right">
<Button variant="btn btn-primary" onClick={this.handleClick}>
Delete
</Button>
</Card.Body>
</Card>
);
}
}
PlayerCard.propTypes = {
player: PropTypes.object.isRequired
};
export default PlayerCard;
So the question is, why does my side not update properly?
I am grateful for any hints. Thanks in advance.
Bonus question: How do I fetch the ip and port dynamically depending on where the node server is running?
Why it's not working:
Because you are not refreshing the list view when the form is submitted.
React is not magic, you need to tell when you want the view to be rerender.
One way to to this is adding a onCreate prop to your playerform.js and run the function when the form is successfully submitted.
import React, { Component } from 'react';
import axios from 'axios';
import PlayerForm from './playerform';
import PlayCard from './playercard';
class PlayerPage extends Component {
constructor(props) {
super(props);
}
state = {
players: []
};
loadPlayers() {
axios
.get('http://localhost:3001/player')
.then(res => {
const players = res.data;
this.setState({ players });
})
.catch(console.log);
}
componentDidMount() {
this.loadPlayers();
}
render() {
return (
<div>
<h2>Add Player</h2>
<PlayerForm onCreate={() => this.loadPlayers()}/>
<hr></hr>
<h2>Available Player</h2>
{this.state.players.map(player => (
<PlayCard player={player} key={player.id} />
))}
</div>
);
}
}
export default PlayerPage;
import React, { Component } from 'react';
import axios from 'axios';
import {
FormControl,
FormGroup,
FormLabel,
Form,
Button
} from 'react-bootstrap';
class PlayerForm extends Component {
state = {
name: '',
nickname: ''
};
handleSubmit = e => {
e.preventDefault();
axios
.post('http://localhost:3001/player', JSON.stringify(this.state), {
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
}
})
.then(() => {
this.props.onCreate();
})
.catch(err => console.log(err));
};
handelChange = e => {
this.setState({
[e.target.name]: e.target.value
});
};
render() {
return (
<Form onSubmit={this.handleSubmit}>
<FormGroup>
<FormLabel>Name</FormLabel>
<FormControl
type="text"
name="name"
placeholder="Enter player name"
onChange={this.handelChange.bind(this)}
/>
</FormGroup>
<FormGroup>
<FormLabel>Nickname</FormLabel>
<FormControl
type="text"
name="nickname"
placeholder="Enter player nickname"
onChange={this.handelChange.bind(this)}
/>
</FormGroup>
<Button variant="btn btn-primary" type="submit">
Add
</Button>
</Form>
);
}
}
export default PlayerForm;
I didn't check if the prop is present but normally you should.
How to handle API route
You should use environments variables:
.env
API_HOST=http://localhost:3001/
process.env.API_HOST
But it's depending on your development environment.
As all suggested, you are not updating the state of the PlayerPage component. Even though answer suggested by #tristan works, I would suggest to reduce API calls as much as possible. My solution would be as following:
PlayerPage.js
import React, { Component } from 'react';
import axios from 'axios';
import PlayerForm from './playerform';
import PlayCard from './playercard';
class PlayerPage extends Component {
constructor(props) {
super(props);
}
state = {
players: []
};
componentDidMount() {
axios
.get('http://localhost:3001/player')
.then(res => {
const players = res.data;
this.setState({ players });
})
.catch(console.log);
}
handleAddNewPlayer = player => {
this.setState(prevState => ({
players: [...prevState.players, player] // or [player, ...prevState.players]
}))
}
handleRemovePlayer = id => {
const players = this.state.filter(player => player.id !== id)
this.setState({ players })
}
render() {
return (
<div>
<h2>Add Player</h2>
<PlayerForm handleAddNewPlayer={this.handleAddNewPlayer}/>
<hr></hr>
<h2>Available Player</h2>
{this.state.players.map(player => (
<PlayCard player={player} key={player.id} handleRemovePlayer={this.handleRemovePlayer} />
))}
</div>
);
}
}
export default PlayerPage;
playerform.js
import React, { Component } from 'react';
import axios from 'axios';
import {
FormControl,
FormGroup,
FormLabel,
Form,
Button
} from 'react-bootstrap';
class PlayerForm extends Component {
state = {
name: '',
nickname: ''
};
handleSubmit = e => {
e.preventDefault();
axios
.post('http://localhost:3001/player', JSON.stringify(this.state), {
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
}
})
.then(data => this.props.handleAddNewPlayer(data)) // adjust this api call to return added user with id
.catch(err => console.log(err));
};
handelChange = e => {
this.setState({
[e.target.name]: e.target.value
});
};
render() {
return (
<Form onSubmit={this.handleSubmit}>
<FormGroup>
<FormLabel>Name</FormLabel>
<FormControl
type="text"
name="name"
placeholder="Enter player name"
onChange={this.handelChange.bind(this)}
/>
</FormGroup>
<FormGroup>
<FormLabel>Nickname</FormLabel>
<FormControl
type="text"
name="nickname"
placeholder="Enter player nickname"
onChange={this.handelChange.bind(this)}
/>
</FormGroup>
<Button variant="btn btn-primary" type="submit">
Add
</Button>
</Form>
);
}
}
export default PlayerForm;
and playercard.js:
import React, { Component } from 'react';
import { Card, Button } from 'react-bootstrap';
import PropTypes from 'prop-types';
import axios from 'axios';
class PlayerCard extends Component {
constructor(props) {
super(props);
this.player = props.player;
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
axios
.delete(`http://localhost:3001/player/${this.player.id}`)
.then(this.props.handleRemovePlayer(this.player.id))
.catch(console.log);
}
render() {
return (
<Card className="flex-row flex-wrap">
<Card.Header>
<Card.Img variant="left" src="https://via.placeholder.com/150" />
</Card.Header>
<Card.Body variant="center">
<Card.Title>{this.player.name}</Card.Title>
<Card.Subtitle>{this.player.nickname}</Card.Subtitle>
</Card.Body>
<Card.Body variant="right">
<Button variant="btn btn-primary" onClick={this.handleClick}>
Delete
</Button>
</Card.Body>
</Card>
);
}
}
PlayerCard.propTypes = {
player: PropTypes.object.isRequired
};
export default PlayerCard;
The lifecycle method componentDidMount() doesn't run in PlayerPage.js every time the state changes, just once when it first loads onto the page. This means the GET request is only run once when the page loads.
You need to either call the GET route again after a POST or DELETE happens, or update the app's state with the data from the POST/DELETE call when it happens to put that data onto the page as well as into the database.
To update the PlayerCard list, the players state must be updated in PlayerPage.
Since the parent has no way to get new players after the Rest API call, it seems that we need a callback function to update the parent component as a child.
I assume you are not using any state management tool(Redux, Flux), and only react is in place and also you don't want to call the api again.
Inside PlayerPage component make a function
addPlayer=(player)=>{
this.setState({
players: {...players, player}
})
}
}
and in render of the same component
Inside PlayerCard
handleClick() {
axios
.delete(`http://localhost:3001/player/${this.player.id}`)
.then((response)=>{
you get your output pass it to props
var player=response.data
this.props.addPlayer(response)
})
.catch(console.log);
}
This example is only with reference to the addPlayer . You can customise it according to your requirement
I have a form that submit some data and i have item component that has a delete button that delete an item but when i submit something it submit and delete the item in the same time
import React, {Component} from 'react';
import ApiClient from './apiClient';
import './MessageForm.css';
class MessageForm extends Component {
constructor(props){
super(props);
this.state = {
submitted: false
}
}
handleSubmit = async (event) => {
event.preventDefault();
const messageData = new FormData(event.target);
await ApiClient.addMessage({
license_plate: messageData.get('license'),
body: messageData.get('body')
});
// console.log("submitted");
// this.props.refreshList();
};
render() {
return(
<form onSubmit={this.handleSubmit} className="MessageForm">
<div>
<label htmlFor="license">License Plate</label>
<input id="license" name="license" type="text" />
</div>
<div>
<label htmlFor="body">Message</label>
<textarea id="body" name="body" type="text"/>
</div>
<div>
<input type="submit" value="Submit"/>
</div>
</form>
)
}
};
export default MessageForm;
this is the item component
import React from 'react';
import moment from 'moment';
import SocailShare from './SocialShare.css'
import { FacebookShareButton, LinkedinShareButton,
TwitterShareButton,
TelegramShareButton,
WhatsappShareButton,
EmailShareButton,} from 'react-share';
import { FacebookIcon, EmailIcon,
TwitterIcon,
TelegramIcon,
WhatsappIcon,
LinkedinIcon,} from 'react-share';
import {
FacebookShareCount,
PinterestShareCount,
VKShareCount,
OKShareCount,
RedditShareCount,
TumblrShareCount,
} from 'react-share';
import './MessageItem.css';
export default ({ id, submission_date, license_plate, body, handleDelete }) => {
var timePosted = moment(submission_date).format('DD/MM/YYYY - HH:mm');
const onDelete = (id) => {
handleDelete(id);
}
return (
<li className="MessageItem">
<span>Time: {timePosted} - </span>
<span>To license: {license_plate} : </span>
<span> {body} </span>
<button onClick={onDelete(id)}>X</button>
<div className="SocialShare">
<FacebookShareButton url="https://github.com/nygardk/react-share#readme" >
<FacebookIcon size={30}/>
<FacebookShareCount url="https://github.com/nygardk/react-share#readme">
{shareCount => (
<span className="myShareCountWrapper">{shareCount}</span>
)}
</FacebookShareCount>
</FacebookShareButton>
<TwitterShareButton url="https://github.com/nygardk/react-share#readme">
<TwitterIcon size={30}/>
</TwitterShareButton >
<EmailShareButton url="https://github.com/nygardk/react-share#readme">
<EmailIcon size={30}/>
</EmailShareButton>
</div>
</li>
);
};
and this is the message list component that renders the message item
import React, { Component } from 'react';
import './MessageList.css';
import MessageItem from './MessageItem';
import ApiClient from './apiClient'
class MessageList extends Component {
constructor(props) {
super(props);
}
handleOnDelete = async (id) => {
console.log(id + "deleted")
await ApiClient.deleteMessage(id);
this.props.refreshList();
}
render() {
const {
messages
} = this.props;
messages.sort(function(a,b){
//the list will be ordered in descending date order (most recent first)
return new Date(b.submission_date) - new Date(a.submission_date);
});
const $messages = messages.map((message) => <MessageItem handleDelete={this.handleOnDelete} key={message._id} {...message} />);
return (
<section className="MessageList">
<h1>Message Board</h1>
<ul>
{$messages}
</ul>
</section>
)
}
}
export default MessageList;
and this is the app component where everything is rendered
import React, { Component} from 'react';
import {BrowserRouter, Route, Switch} from 'react-router-dom';
import ApiClient from './apiClient';
import './App.css';
import MessageForm from './MessageForm';
import MessageList from './MessageList';
class App extends Component {
constructor(props){
super(props);
this.state = {
messages: []
}
}
componentDidMount = async () => {
this.refreshList();
}
refreshList = async () => {
const messages = await ApiClient.getMessages();
this.setState({
messages
})
}
render () {
return (
<BrowserRouter>
<div className="App">
<header className="App-header">
<h1>Hello License</h1>
<p>Send your messages to a plate number easily!</p>
</header>
<MessageForm refreshList = {this.refreshList}/>
</div>
<Switch>
<Route exact path="/api" render ={props => <MessageList refreshList = {this.refreshList} messages={this.state.messages} {...props}/> }/>
</Switch>
</BrowserRouter>
)
}
};
export default App;
in your item component, this line <button onClick={onDelete(id)}>X</button> is your problem.
What you are inadvertently saying is that when the DOM renders this component, it should call onDelete right away, and the onClick handler will refer to void. To avoid this, what you want is to pass in a function like so: <button onClick={(id) => onDelete(id)}>X</button>