ASP.NET Core API and React JS - reactjs

I have created ASP.NET Core API and React CURD practice example. I am following this example
but I've used react semantic ui for view. I am new to react and ASP.NET any suggestion so that I can improve my code.
I am able to fetch,POST,PUT and DELETE customer record but there are some small issues or point that I don't know how to implement. Those are as following
1 - I have used Modal so I can open form as popup (AddCustomer is form to add and edit record) in that I have two functions to OPEN and CLOSE the Modal but I don't how to call them from Customer.js and also on successful POST,PUT, DELETE request.
2 - When I open FORM to ADD or EDIT record I am not able to store that in state. When I try to type in input field it does not store in name and address.
3 - Also you can see in Customer.js I am hiding the form and delete modal but I want to close them on POST, PUT and DELETE task completion.
This is Customer.js
import React from 'react';
import AddCustomer from './AddCustomer';
import CustomerView from './CustomerView';
import DeleteRecord from './DeleteRecord';
export default class Customer extends React.Component {
constructor(props) {
super(props);
this.state = {
isAddCustomer:false,
isEditCustomer:false,
isDeleteCustomer:false,
closeForm:false,
singleCustomer:{},
deleteId:{}
}
}
onCreate = () => {
console.log("is add customer true ")
this.setState({isAddCustomer:true})
}
onFormControl = () =>{
this.setState({
isAddCustomer:false,
isEditCustomer:false
})
}
onDeleteClick = customerId => {
const headerTitle = "Customer";
console.log("onDeleteClick")
this.setState({
isDeleteCustomer:true,
deleteId:{
ID:customerId,
title:headerTitle,
open:true
}
});
}
//New Customer record
onAddFormSubmit = data => {
console.log("In add form submit")
console.log(data)
let customerApi = 'https://localhost:44387/api/Customers';
let method = '';
if(this.state.isEditCustomer){
console.log("In Edit api")
console.log(this.state.editCustomerId)
customerApi = 'https://localhost:44387/api/Customers/' + this.state.editCustomerId;
method = 'PUT'
}else{
console.log("In Add api")
customerApi = 'https://localhost:44387/api/Customers';
method = 'POST'
}
const myHeader = new Headers({
'Accept':'application/json',
'Content-type':'application/json'
});
fetch(customerApi,{
method:method,
headers:myHeader,
body:JSON.stringify(data)
})
.then(res => res.json())
.then(
(result) => {
this.setState({
users:result,
isAddCustomer:false,
isEditCustomer:false
})
},(error) => {
this.setState({ error });
}
)
}
//Edit customer record
onEditCustomer = customerId => {
//Get ID, name and address
this.setState({
editCustomerId:customerId
});
const customerApi = 'https://localhost:44387/api/Customers/'+customerId;
const myHeader = new Headers({
'Accept':'application/json',
'Content-type':'application/json; charset=utf-8'
});
fetch(customerApi,{
method:'GET',
headers:myHeader
})
.then(res => res.json())
.then(
(result) => {
this.setState({
isEditCustomer:true,
isAddCustomer:false,
singleCustomer:{
customer:result,
isEditCustomer:true
}
})
},(error) => {
this.setState({ error });
}
)
}
//Delete Customer
onDeleteCustomer = customerId => {
const customerApi = 'https://localhost:44387/api/Customers/'+customerId;
const myHeader = new Headers({
'Accept':'application/json',
'Content-type':'application/json; charset=utf-8'
});
fetch(customerApi,{
method:'DELETE',
headers:myHeader
})
.then(res => res.json())
.then(
(result) => {
this.setState({
isDeleteCustomer:false
})
},(error) => {
this.setState({ error });
}
)
}
render() {
let form;
if(this.state.isAddCustomer && !this.state.isEditCustomer){
console.log("Add")
form = <AddCustomer onAddFormSubmit={this.onAddFormSubmit}
isAddCustomer = {this.state.isAddCustomer}
onFormControl = {this.onFormControl}/>
}else if(this.state.isEditCustomer && !this.state.isAddCustomer){
console.log("Edit")
form = <AddCustomer onAddFormSubmit={this.onAddFormSubmit}
singleCustomer = {this.state.singleCustomer}
onFormControl = {this.onFormControl}/>
}else if(this.state.isDeleteCustomer){
console.log("Delete")
console.log(this.state.deleteId)
form = <DeleteRecord onDeleteCustomer={this.onDeleteCustomer}
deleteId = {this.state.deleteId}
/>
}
return (
<div>
{form}
<br/>
<CustomerView
onEditCustomer = {this.onEditCustomer}
onCreate = {this.onCreate}
onDeleteClick = {this.onDeleteClick}/>
</div>
)
}
}
Here is CustomerView.js
import React from 'react';
import { Table, Button } from 'semantic-ui-react';
export default class CustomerView extends React.Component {
constructor(props) {
super(props);
this.state = {
error: null,
deleteTitle: "customer",
isLoaded: false,
formClose: false,
singleCustomer: [],
users: []
}
}
//fetch data
componentDidMount() {
const customerApi = 'https://localhost:44387/api/Customers';
const myHeader = new Headers();
myHeader.append('Content-type', 'application/json');
myHeader.append('Accept', 'application/json');
myHeader.append('Origin', 'https://localhost:44387');
const options = {
method: 'GET',
myHeader
};
fetch(customerApi, options)
.then(res => res.json())
.then(
(result) => {
this.setState({
users: result,
isLoaded: true
});
},
(error) => {
this.setState({
isLoaded: false,
error
});
}
)
}
//Delete Customer
onDeleteCustomer = customerId => {
const { users } = this.state;
this.setState({
users: users.filter(customer => customer.customerId !== customerId)
});
const customerApi = 'https://localhost:44387/api/Customers/' + customerId;
const myHeader = new Headers({
'Accept': 'application/json',
'Content-type': 'application/json; charset=utf-8'
});
fetch(customerApi, {
method: 'DELETE',
headers: myHeader
})
.then(res => res.json())
.then(
(result) => {
this.setState({
})
}, (error) => {
this.setState({ error });
}
)
}
render() {
const { users } = this.state;
return (
<div>
<Button color='blue' onClick={() => this.props.onCreate()}>New Customer</Button>
<br/>
<br/>
<Table celled textAlign='center'>
<Table.Header>
<Table.Row>
<Table.HeaderCell>ID</Table.HeaderCell>
<Table.HeaderCell>Name</Table.HeaderCell>
<Table.HeaderCell>Address</Table.HeaderCell>
<Table.HeaderCell>Action</Table.HeaderCell>
<Table.HeaderCell>Action</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body >
{
users.map(user => (
<Table.Row key={user.customerId}>
<Table.Cell>{user.customerId}</Table.Cell>
<Table.Cell>{user.name}</Table.Cell>
<Table.Cell>{user.address}</Table.Cell>
<Table.Cell>
<Button color='blue' onClick={() =>
this.props.onEditCustomer(user.customerId)}>Edit</Button>
</Table.Cell>
<Table.Cell>
<Button color='red' onClick={() =>
this.props.onDeleteClick(user.customerId)}>Delete</Button>
</Table.Cell>
</Table.Row>
))
}
</Table.Body>
<Table.Footer>
<Table.Row>
<Table.HeaderCell colSpan='5'>
No of Pages
</Table.HeaderCell>
</Table.Row>
</Table.Footer>
</Table>
</div>
)
}
}
Here is AddCustomer.js
import React from 'react';
import { Button, Form, Modal } from 'semantic-ui-react';
export default class AddCustomer extends React.Component {
constructor(props) {
super(props);
this.state = {
showCreateForm: false,
addOrdit:false,
id: "",
name: "",
address: "",
formData: {},
record: {}
}
if (props.isAddCustomer){
this.state.showCreateForm = props.isAddCustomer;
}
else if (props.singleCustomer) {
console.log("Single customer")
console.log(props.singleCustomer)
this.state.id = props.singleCustomer.customer.customerId;
this.state.name = props.singleCustomer.customer.name;
this.state.address = props.singleCustomer.customer.address;
this.state.record = props.singleCustomer.customer;
this.state.showCreateForm = props.singleCustomer.isEditCustomer;
this.state.addOrdit = props.singleCustomer.isEditCustomer;
console.log(this.state.name)
}else if(props.closeForm){
this.state.showCreateForm = props.closeForm;
}
}
handleChangeName = event => {
const value = event.target.value;
this.setState({ name:value });
}
handleChangeAddress = event => {
const value = event.target.value;
this.setState({ address:value });
}
handleSubmit = event => {
event.preventDefault();
if(this.state.addOrdit){
this.setState({
record: {
customerId: this.state.id,
name: this.state.name,
address: this.state.address
}
});
this.props.onAddFormSubmit(this.state.record);
}else{
this.setState({
formData: {
name: this.state.name,
address: this.state.address
}
});
this.props.onAddFormSubmit(this.state.formData);
}
}
//On cancel button click close Create user form
closeCreateForm = () => {
this.setState({ showCreateForm: false })
this.props.onFormControl();
}
//Open Create new Customer form
openCreateCustomer = () => {
this.setState({ showCreateForm: true })
}
render() {
let formTitle;
if (this.state.id !== 0) {
formTitle = "Edit Customer";
} else {
formTitle = "New Customer";
}
return (
<div>
<Modal size='small'
closeOnTriggerMouseLeave={false}
open={this.state.showCreateForm}>
<Modal.Header>
{formTitle}
</Modal.Header>
<Modal.Content>
<Form onSubmit={this.handleSubmit}>
<Form.Field>
<label>Name</label>
<input type="text" placeholder='Name' name="name"
value={this.state.name}
onChange={this.handleChangeName} />
</Form.Field>
<Form.Field>
<label>Address</label>
<input type="text" placeholder='Address' name="address"
value={this.state.address}
onChange={this.handleChangeAddress} />
</Form.Field>
<br />
<Button type='submit' floated='right' color='green'>Create</Button>
<Button floated='right' onClick={this.closeCreateForm}
color='black'>Cancel</Button>
<br />
</Form>
</Modal.Content>
</Modal>
</div>
)
}
}
And last one DeleteRecord.js
import React from 'react';
import { Button, Modal, Icon } from 'semantic-ui-react';
export default class DeleteRecord extends React.Component {
constructor(props) {
super(props);
this.state = {
ID:'',
title: "",
open: false
}
if(props.deleteId){
console.log(props.deleteId)
this.state.ID = props.deleteId.ID;
this.state.title = props.deleteId.title;
this.state.open = props.deleteId.open;
}
}
//On cancel button click close Create user form
closeCreateForm = () => {
console.log("Clicked")
this.setState({ open: false })
}
//Open Create new Customer form
openCreateCustomer = () => {
this.setState({ open: true })
}
render() {
const title = "Delete " + this.state.title;
return (
<div>
<Modal size='small'
closeOnTriggerMouseLeave={false}
open={this.state.open}>
<Modal.Header>
{title}
</Modal.Header>
<Modal.Content>
<br />
Are you sure?
<br />
<br />
<Button floated='right' icon labelPosition='right' color='red'
value='true'
onClick={() => this.props.onDeleteCustomer(this.state.ID)}
>
<Icon name='close' />
Delete
</Button>
<Button floated='right' color='black'
value='false'
onClick={this.closeCreateForm}
>Cancel</Button>
<br />
<br />
</Modal.Content>
</Modal>
</div>
)
}
}

Try using mobx for managing state variables and axios for making calls to API this will resolve your problem.
sample example code for using mobx
import { observable, computed } from "mobx"
class OrderLine {
#observable price = 0
#observable amount = 1
#computed get total() {
return this.price * this.amount;
}
}
now you can import OrderLine class in your Js and you can use and manage the state of price, amount dynamically for rendering the UI
Go through below link
https://mobx.js.org/README.html

Related

Detect when Firebase sign in has finished or canceled in this scenario

I learn ReactJs and now I must have Firebase signin. I have a design question on how to detect when Firebase linkWithPopup finish. User press Button and my anonymous user Firebase UID will be turned into a Google credential one.
The linkWithPopup pops up and user select one Google account to use.
I must detect when this process finish or aborted.
Here is my code:
This method get's called when user click Button for Google signin:
onSocialLoginLink = provider => {
const { firebase, changeUserRole } = this.props;
firebase.auth.currentUser
.linkWithPopup(firebase[provider])
// .linkWithRedirect(this.props.firebase[provider])
.then(changeUserRole())
.then(this.fetchSignInMethods)
.catch(error => this.setState({ error }));
};
The problem I encounter is that changeUserRole() gets called before linkWithPopup returns since linkWithPopup of course run asynchronous. This means that my user get this new roll from changeUserRole() even if User select to abort signin.
I must detect when this process finish or aborted.
What would be a recommended best way to do this?
My ide is that if I can detect maybe when signin window looses focus and regain focus I could look at if Firebase user have changed provider to Google or is still anonymous user? Is this doable?
This is the Component that handle signin:
/* eslint-disable max-classes-per-file */
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { compose } from 'recompose';
import { AuthUserContext, withAuthorization } from '../../session';
import { withFirebase } from '../../firebase';
import { SIGN_IN_METHODS } from '../../constants/signinmethods';
import * as ROLES from '../../constants/roles';
import '../../styles/link-account.scss';
import { changeToUserRole } from '../../redux/userData/user.actions';
class LoginManagementBase extends Component {
constructor() {
super();
this.state = {
activeSignInMethods: [],
anonymousSignIn: null,
error: null,
};
}
componentDidMount() {
this.fetchSignInMethods();
}
fetchSignInMethods = () => {
const { firebase, authUser } = this.props;
const email = authUser.email === null ? 'none#guest.ac' : authUser.email;
firebase.auth
.fetchSignInMethodsForEmail(email)
.then(activeSignInMethods =>
this.setState({
activeSignInMethods,
anonymousSignIn: activeSignInMethods.length === 0,
error: null,
}),
)
.catch(error => this.setState({ error }));
};
onSocialLoginLink = provider => {
const { firebase, changeUserRole } = this.props;
firebase.auth.currentUser
.linkWithPopup(firebase[provider])
// .linkWithRedirect(this.props.firebase[provider])
.then(changeUserRole())
.then(this.fetchSignInMethods)
.catch(error => this.setState({ error }));
};
onDefaultLoginLink = password => {
const { firebase, authUser } = this.props;
const credential = firebase.emailAuthProvider.credential(authUser.email, password);
firebase.auth.currentUser
.linkAndRetrieveDataWithCredential(credential)
.then(this.fetchSignInMethods)
.catch(error => this.setState({ error }));
};
onUnlink = providerId => {
const { firebase } = this.props;
firebase.auth.currentUser
.unlink(providerId)
.then(this.fetchSignInMethods)
.catch(error => this.setState({ error }));
};
render() {
const { activeSignInMethods, error } = this.state;
const { saveRolesErr, isSavingRole } = this.props;
// if (isSavingRole) return null;
return (
<div className="provideToggler">
<h1>
You are signed in Anonymously!
<br />
Changes you do is only saved in this browser.
<br /> If you want to access your progress anywhere please sign in below!
</h1>
<ul>
{SIGN_IN_METHODS.map(signInMethod => {
const onlyOneLeft = activeSignInMethods.length === 1;
const isEnabled = activeSignInMethods.includes(signInMethod.id);
return (
<li key={signInMethod.id}>
{signInMethod.id === 'password' ? (
<DefaultLoginToggle
// accountEmail={this.props.authUser.email}
onlyOneLeft={onlyOneLeft}
isEnabled={isEnabled}
signInMethod={signInMethod}
onLink={this.onDefaultLoginLink}
onUnlink={this.onUnlink}
/>
) : (
<SocialLoginToggle
onlyOneLeft={onlyOneLeft}
isEnabled={isEnabled}
signInMethod={signInMethod}
onLink={this.onSocialLoginLink}
onUnlink={this.onUnlink}
/>
)}
</li>
);
})}
</ul>
<h1 style={{ color: 'red' }}>
{error && error.message}
{saveRolesErr && saveRolesErr.message}
</h1>
</div>
);
}
}
const SocialLoginToggle = ({ onlyOneLeft, isEnabled, signInMethod, onLink, onUnlink }) =>
isEnabled ? (
<button type="button" onClick={() => onUnlink(signInMethod.id)} disabled={onlyOneLeft}>
Unlink <i className={signInMethod.icon} aria-hidden="true" /> {signInMethod.name} sign in
</button>
) : (
<button type="button" onClick={() => onLink(signInMethod.provider)}>
Link <i className={signInMethod.icon} aria-hidden="true" /> {signInMethod.name} sign in
</button>
);
// TODO This is not in use but might use it later
class DefaultLoginToggle extends Component {
constructor() {
super();
this.state = { passwordOne: '', passwordTwo: '' };
}
onSubmit = event => {
const { passwordOne } = this.state;
const { onLink } = this.props;
event.preventDefault();
onLink(passwordOne);
this.setState({ passwordOne: '', passwordTwo: '' });
};
onChange = event => {
this.setState({ [event.target.name]: event.target.value });
};
render() {
const { signInMethod } = this.props;
const { passwordOne, passwordTwo } = this.state;
const isInvalid = passwordOne !== passwordTwo || passwordOne === '';
return (
<form onSubmit={this.onSubmit}>
Link <i className={signInMethod.icon} aria-hidden="true" /> {signInMethod.name} sign in
<input
name="passwordOne"
value={passwordOne}
onChange={this.onChange}
type="password"
placeholder="Password for email sign in"
/>
<input
name="passwordTwo"
value={passwordTwo}
onChange={this.onChange}
type="password"
placeholder="Confirm New Password"
/>
<button disabled={isInvalid} type="submit">
Save password for email sign in
</button>
</form>
);
}
}
const mapDispatchToProps = dispatch => ({
changeUserRole: () => dispatch(changeToUserRole()),
});
const mapStateToProps = state => {
return {
isSavingRole: state.user.isSavingRoles,
saveRolesErr: state.user.saveRolesErrMsg,
};
};
const enhance = compose(withFirebase, connect(mapStateToProps, mapDispatchToProps));
const LoginManagement = enhance(LoginManagementBase);
const LinkAccounts = () => (
<AuthUserContext.Consumer>
{authUser => (
<div>
<LoginManagement authUser={authUser} />
</div>
)}
</AuthUserContext.Consumer>
);
const condition = authUser => authUser && authUser.roles.includes(ROLES.ANON);
export default withAuthorization(condition)(LinkAccounts);
if that's the problem you can add a condition to check than change the role
const { firebase, changeUserRole } = this.props;
firebase.auth.currentUser
.linkWithPopup(firebase[provider])
.then(res=>{
if(res.credential){changeUserRole()}
})
.then(this.fetchSignInMethods)
.catch(error => this.setState({ error }));
};```

optimistic ui updates - react

I imagine this is a basic in react but I'm not sure how to get it to work, basically when I delete, create or edit anything in my components I want the change to happen in realtime without refreshing the page, I've achieved it at some level with the search function but not entirely sure how to do with for the delete function for example:
Here is what I'm working with, how would I get this to work with my axios delete function?
Thanks
import { connect } from 'react-redux';
import { fetchTournaments } from '../actions/tournaments';
import Item from './Item';
import EditTournament from './EditTournament';
import axios from 'axios';
import '../styles/Item.css';
class SearchAndDisplay extends React.PureComponent {
componentDidMount() {
this.props.fetchTournaments();
}
state = {
searchCriteria: '',
isLoading: false
};
handleChange = event => {
this.setState({
searchCriteria: event.target.value
});
};
async handleDelete(id) {
const url = `http://localhost:4000/tournaments/`;
await axios
.delete(url + id)
.then(res => {
console.log(res.data);
})
.catch(err => {
console.log(err);
});
}
formatDate(date) {
let options = {
year: 'numeric',
month: 'numeric',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
second: 'numeric',
hour12: false
};
let newDate = new Date(Date.parse(date));
let format = new Intl.DateTimeFormat('default', options).format(newDate);
return format;
}
handleChange = event => {
this.setState({ searchCriteria: event.target.value });
};
renderList() {
let tournmentsArray = this.props.tournaments;
const filterTournaments = tournmentsArray.filter(item =>
item.name.includes(this.state.searchCriteria)
);
if (filterTournaments === undefined || filterTournaments.length === 0) {
return (
<React.Fragment>
<div className="notFound">
Something went wrong.
<br />
<button
className="notFoundButton"
onClick={() => {
this.setState({ searchCriteria: '' });
}}
>
Retry
</button>
</div>
</React.Fragment>
);
} else {
return filterTournaments.map(item => (
<Item
key={item.name}
name={item.name}
organizer={item.organizer}
participants={Object.values(item.participants)}
game={item.game}
start={this.formatDate(item.startDate)}
>
<div className="buttonBar">
<EditTournament id={item.id} />
<button
className="button"
onClick={() => {
if (
window.confirm('Are you sure you want to delete this item?')
) {
this.handleDelete(item.id);
}
}}
>
Delete
</button>
</div>
</Item>
));
}
}
render() {
return (
<div className="container">
<input
onChange={this.handleChange}
className="input"
placeholder="Search..."
id="searchField"
value={this.state.searchCriteria}
/>
<div className="row">{this.renderList()}</div>
</div>
);
}
}
function mapStateToProps({ tournaments }) {
return {
tournaments: Object.values(tournaments).flat()
};
}
export default connect(mapStateToProps, {
fetchTournaments
})(SearchAndDisplay);
unlike delete the create and edit data is handled by redux like so:
Create tournament:
import { reduxForm, Field } from 'redux-form';
import '../styles/promptForms.css';
import '../styles/Header.css';
import { connect } from 'react-redux';
import { createTournaments } from '../actions/tournaments';
class CreateTournamentPromptFrom extends React.Component {
constructor(props) {
super(props);
this.state = {
showHide: false
};
}
createTournamentButton() {
return (
<div>
<button
className="genericButton"
onClick={() => this.setState({ showHide: true })}
>
CREATE TOURNAMENT
</button>
</div>
);
}
renderInput = ({ input, label }) => {
return (
<div>
<label>{label}</label>
<br />
<input className="promptInput" {...input} autoComplete="off" />
</div>
);
};
onSubmit = formValues => {
this.props.createTournaments(formValues);
};
render() {
const { showHide } = this.state;
return (
<React.Fragment>
<div className={`overlay ${showHide ? 'toggle' : ''} `} />
<div className={`promptBox ${showHide ? 'toggle' : ''} `}>
<h3>localhost:3000 says</h3>
<form onSubmit={this.props.handleSubmit(this.onSubmit)}>
<Field
name="name"
component={this.renderInput}
label="Enter Tournament:"
/>
<button className="okayButton">OK</button>
</form>
<button
className="cancelButton"
onClick={() => this.setState({ showHide: false })}
>
Cancel
</button>
</div>
{this.createTournamentButton()}
</React.Fragment>
);
}
}
const formWrapped = reduxForm({
form: 'promptForm'
})(CreateTournamentPromptFrom);
export default connect(null, { createTournaments })(formWrapped);
actions:
import {
FETCH_TOURNAMENTS,
FETCH_TOURNAMENT,
CREATE_TOURNAMENT,
EDIT_TOURNAMENT
} from './types';
import { API_TOURNAMENTS_URL } from '../constants/api';
import axios from 'axios';
export const fetchTournaments = () => async dispatch => {
const response = await axios.get(API_TOURNAMENTS_URL);
dispatch({
type: FETCH_TOURNAMENTS,
payload: response.data.flat()
});
};
export const fetchTournament = id => async dispatch => {
const response = await axios.get(`http://localhost:4000/tournaments/${id}`);
dispatch({ type: FETCH_TOURNAMENT, payload: response.data });
};
export const createTournaments = formValues => async dispatch => {
const response = await axios.post(API_TOURNAMENTS_URL, {
...formValues
});
dispatch({ type: CREATE_TOURNAMENT, payload: response.data });
};
export const editTournaments = (id, formValues) => async dispatch => {
const response = await axios.patch(
`http://localhost:4000/tournaments/${id}`,
formValues
);
dispatch({ type: EDIT_TOURNAMENT, payload: response.data });
};
reducers:
import _ from 'lodash';
import {
FETCH_TOURNAMENT,
CREATE_TOURNAMENT,
FETCH_TOURNAMENTS,
EDIT_TOURNAMENT,
DELETE_TOURNAMENT
} from '../actions/types';
export default (state = {}, action) => {
switch (action.type) {
case FETCH_TOURNAMENT:
return { ...state, [action.payload.id]: action.payload };
case FETCH_TOURNAMENTS:
return { ...state, [action.payload.id]: action.payload };
case CREATE_TOURNAMENT:
return { ...state, [action.payload.id]: action.payload };
case EDIT_TOURNAMENT:
return { ...state, [action.payload.id]: action.payload };
case DELETE_TOURNAMENT:
return _.omit(state, action.payload);
default:
return state;
}
};
To "optimistically" delete an item from state you'll need to immediately delete it from state to reflect the change right away in the UI. BUT you will need to add extra redux state to "hold" a pending delete with your backend. When the delete is successful you clear the held delete, if it fails you clear the held delete and add it back in to your regular data (and perhaps display some error message or toast, etc..).
I see you don't do the delete via redux, so use local component state and you'll have to filter your tournament data when rendering.
class SearchAndDisplay extends PureComponent {
componentDidMount() {
this.props.fetchTournaments();
}
state = {
searchCriteria: "",
isLoading: false,
optimisticTournaments: null // <-- state to hold temp "deleted" data
};
handleChange = event => {
this.setState({
searchCriteria: event.target.value
});
};
async handleDelete(id) {
console.log("delete id", id);
// optimistically remove element
this.setState({
optimisticTournaments: this.props.tournaments.filter(
item => item.id !== id
)
});
await axios
.delete(url + id)
.then(res => {
console.log(res.data);
// Need to create a call back to let parent know element was deleted
this.props.deleteSuccess(id);
})
.catch(err => {
console.log(err);
alert("Failed to delete");
})
.finally(() => {
this.setState({ optimisticTournaments: null });
});
}
formatDate(date) {
let options = {
year: "numeric",
month: "numeric",
day: "numeric",
hour: "numeric",
minute: "numeric",
second: "numeric",
hour12: false
};
let newDate = new Date(Date.parse(date));
let format = new Intl.DateTimeFormat("default", options).format(newDate);
return format;
}
handleChange = event => {
this.setState({ searchCriteria: event.target.value });
};
renderList() {
let tournmentsArray =
this.state.optimisticTournaments || this.props.tournaments;
const filterTournaments = tournmentsArray.filter(item =>
item.name.includes(this.state.searchCriteria)
);
if (filterTournaments === undefined || filterTournaments.length === 0) {
return (
<React.Fragment>
<div className="notFound">
Something went wrong.
<br />
<button
className="notFoundButton"
onClick={() => {
this.setState({ searchCriteria: "" });
}}
>
Retry
</button>
</div>
</React.Fragment>
);
} else {
return filterTournaments.map(item => (
<Item
key={item.name}
name={item.name}
organizer={item.organizer}
participants={Object.values(item.participants)}
game={item.game}
start={this.formatDate(item.startDate)}
>
<div className="buttonBar">
<EditTournament id={item.id} />
<button
className="button"
onClick={() => {
if (
window.confirm("Are you sure you want to delete this item?")
) {
this.handleDelete(item.id);
}
}}
>
Delete
</button>
</div>
</Item>
));
}
}
render() {
return (
<div className="container">
<input
onChange={this.handleChange}
className="input"
placeholder="Search..."
id="searchField"
value={this.state.searchCriteria}
/>
<div className="row">{this.renderList()}</div>
</div>
);
}
}
Here is how you can do using forceUpdate() (Since you don't want to use state):
import React, { Component } from 'react';
import { render } from 'react-dom';
class App extends Component {
constructor() {
super();
this.items = [
{id: 1, name: "item 1"},
{id: 2, name: "item 2"},
];
this.handleDelete = this.handleDelete.bind(this);
}
handleDelete(index) {
const newState = [...this.items];
delete newState[index];
// or
// newState.splice(index, 1);
this.items = newState;
this.forceUpdate();
}
render() {
return (
<div>
{
this.items.map((item, index) => {
return (
<div>
{item.name}
<button onClick={() => this.handleDelete(index)}>Delete item</button>
</div>
)
})
}
</div>
);
}
}
render(<App />, document.getElementById('root'));
Just pass the index in the map method :
...map((item, index) => ...);
Do it in the then() after your axios call.
Note that the documentation highly advice you to avoid using forceUpdate() when you can, so you really should use a state for this, I don't see any good reason for you not to use it here.
Here is a quick repro on Stackblitz.

React Redux showing data in table from API

Currently, my application showing initialState data in the table and those data are hardcoded. I want to show my API fetched data in the table.
this is my postReducer.js file:
var initialState = {
employees: [
{ id: 1, name: 'jhon', age: '23'},
{ id: 2, name: 'doe', age: '24'}
]
};
var postReducer = (state = initialState, action) => {
switch (action.type) {
case 'ADD_POST':
return {
...state,
employees: [...state.employees, action.data],
};
case 'EDIT_POST':
return {
...state,
employees: state.employees.map(emp => emp.id === action.data.id ? action.data : emp)
};
case 'DELETE_POST':
console.log(action.data.id)
return {
...state,
employees: [...state.employees.filter((post)=>post.id !== action.data.id)],
};
default:
return state;
}
};
export default postReducer;
and this is my table.js file
import React, {Fragment} from "react";
import { connect } from "react-redux";
class Table extends React.Component {
onEdit = (item) => { //Use arrow function to bind `this`
this.props.selectedData(item);
}
onDelete = (id) => {
const data = {
id,
}
this.props.dispatch({ type: 'DELETE_POST', data });
}
render() {
return (
<Fragment>
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Age</th>
<th scope="col">Edit</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
{this.props.employees.map((item, index) => (
<tr key={index}>
<td>{item.name}</td>
<td>{item.age}</td>
<td>
<button
type="button"
onClick={() => this.onEdit(item)}>EDIT
</button>
<button
onClick={ () => this.onDelete(item.id) }>DELETE
</button>
</td>
</tr>
))}
</tbody>
</Fragment>
);
}
}
const mapStateToProps = (state) => ({ employees: state.employees });
export default connect(mapStateToProps)(Table);
and this my form.js file
import React, { Fragment } from "react"
import { connect } from 'react-redux'
const axios = require('axios');
class Form extends React.Component {
constructor(props) {
super(props);
this.state = {
id: this.props.selectedData.id,
name: this.props.selectedData.name,
age: this.props.selectedData.age,
};
this.onHandleChange = this.onHandleChange.bind(this);
this.submit = this.submit.bind(this);
}
submit(event) {
const data = {
name: this.state.name,
age: this.state.age,
email: this.state.email
};
if (this.props.isEdit) {
data.id = this.props.selectedData.id;
console.log('edit', data);
this.props.dispatch({ type: 'EDIT_POST', data })
} else {
// generate id here for new emplyoee
this.props.dispatch({ type: 'ADD_POST', data })
}
}
onHandleChange(event) {
this.setState({
[event.target.name]: event.target.value
});
}
componentDidUpdate(prevProps) {
if (prevProps.selectedData.age !== this.props.selectedData.age) { //Check on email, because email is unique
this.setState({ name: this.props.selectedData.name, age: this.props.selectedData.age })
}
}
render() {
return (
<form>
<div className="form-group">
<input onChange={(event) => this.onHandleChange(event)} value={this.state.name} name="name" type="text" />
</div>
<div className="form-group">
<input onChange={(event) => this.onHandleChange(event)} value={this.state.age} name="age" type="number" />
</div>
<button onClick={(event) => this.submit(event)} type="button">
{this.props.isEdit ? 'Update' : 'SAVE'}
</button>
</form>
);
}
}
export default connect(null)(Form);
I think i need to work on table.js file to implement, i tried with componentDidmount but i failed to implement is.
I am using Axios for http request
this is the request snippet with api:
axios.get('http://127.0.0.1:8000/api/v1/employee/')
.then(function (response) {
// handle success
})
.catch(function (error) {
// handle error
})
.finally(function () {
});
I am not getting how to successfully implement this like when i visit the page, i should see the table with data that come from api endpoint.
Can anyone help me regarding this?
In your Table component, you can make use of componentDidMount for your API call,
componentDidMount(){
axios.get('http://127.0.0.1:8000/api/v1/employee/')
.then((response) => { //Use arrow function to auto bind `this`
// handle success
this.props.dispatch({ type: 'ADD_POST', response.data }) //considering response.data is the correct array
})
.catch(function (error) {
// handle error
})
.finally(function () {
});
}

How to modify state of a component from a routed component?

I'm building a react native application where users can create event and invite people.
But I'm having some problems modifying the state of the component from a routed component.
There is a createEvent screen where user tries to create event...on clicking on invite people button a new screen is rendered let's name it as invitePeopleScreen.
If i'm not wrong...I think I need to use redux.
The createEvent screen:
import React from 'react';
import {Button, Container, Form, Input, Item, Text, Textarea} from "native-base";
export default class createEventScreen extends React.Component{
constructor(props) {
super(props);
this.state = {
title: '',
description: '',
createdBy: '',
invites: []
};
}
createEvent () {}
render() {
return (
<Container>
<Text>Create An Event </Text>
<Form>
<Item rounded>
<Input keyboardType="default"
placeholder="Enter the event title"
onChangeText={ title => this.setState({ title: title }) } />
</Item>
<Item rounded>
<Textarea keyboardType="default"
placeholder="Enter the event description"
onChangeText={ description => this.setState({ description: description }) } />
</Item>
</Form>
<Button rounded onPress={ () => { this.props.navigation.navigate('invitePeople') }}>
<Text>Invite People</Text>
</Button>
<Button rounded onPress={this.createEvent}>
<Text>Create Event</Text>
</Button>
</Container>
)
}
}
Here is the invitePeople Screen:
import React from 'react';
import { Container } from "native-base";
export default class inviteUsersScreen extends React.Component {
constructor(props) {
super(props);
this.state = {
username: ''
}
}
addInvite = (username) => {
// push the username into the invites array of createEventScreen
}
render() {
return (
<Container>
<Text>Invite People </Text>
<Form>
<Item rounded>
<Input keyboardType="default"
placeholder="Enter the username"
onChangeText={ (username) => {this.setState({ username: username)})} />
</Item>
</Form>
<Button rounded onPress={ () => { this.addInvite(this.state.username) }}>
<Text>Submit</Text>
</Button>
);
}
}
I am exactly not sure what code will go into addInvite function.
You have three options to achieve this, option 1: pass a function as a prop to the next screen, option 2: use async storage, option 3: Redux.
Option: 1.
in your CreateEventScreen pass a function to the second screen as prop.
addInvite = username => {
this.setState({
invites: [...this.state.invites, username],
});
};
<Button
rounded
onPress={() => {
this.props.navigation.navigate('InvitePeople', {
addInvite: this.addInvite,
});
}}>
<Text>Invite People</Text>
</Button>
In your InviteUsersScreen, get the function and pass username.
addInvite = username => {
// push the username into the invites array of createEventScreen
const addInvite = this.props.navigation.getParam('addInvite', () => {
alert('No function was passed');
});
addInvite(username);
};
Option 2: AsyncStorage
import {AsyncStorage} from 'react-native';
// create function for saving items to storage
export const SaveItem = async (key, value) => {
try {
await AsyncStorage.setItem(key, value);
console.log('saved')
} catch(e) {
console.log(e)
}
};
// create function for saving items to storage
export const ReadItem = async key => {
try {
var result = await AsyncStorage.getItem(key);
return result;
} catch (e) {
return e;
}
};
Then you can add items to the storage in your InviteUsersScreen.
addInviteWithStorge = username => {
const usernames = [...this.state.storageUsernames, username];
this.setState({
storageUsernames: usernames,
username: '',
});
const invites = JSON.stringify(usernames);
SaveItem('invites', invites)
.then(res => {
console.log('saved', res);
})
.catch(e => console.warn(e));
};
Retrieve items in your CreateEventScreen.
ReadItem('invites')
.then(res => {
if (res) {
const invites = JSON.parse(res);
this.setState({
invites: invites,
});
}
})
.catch(e => console.warn(e));
Demo
Something like that perhaps:
addInvite = (e, username) => {
e.preventDefault();
this.setState({
list: [...this.state.list, username]
})
}
Assuming you add to your state a list prop

Reactjs Event/Action Button not switching as expected

Reactjs Event/Action Button not switching as expected.
Am trying to add follow and unfollow action button. when I post via axios via Follow button,
it post to data to server backend and return success message. Then the Follow button switched to Unfollow button.
Now my problem is that Unfollow button is not switching back to Follow Button when User try to unfollow someone.
Please what am I doing wrong here.
here is the json success message
[{"status":"success", "follow":"1", "unfollow":"0"}]
here is the my code
import React, { Component, Fragment } from "react";
import { render } from "react-dom";
import axios from 'axios';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
result_data: '',
data: [],
loading: false
};
}
componentDidMount() {
this.setState({
data: [{"uid":"1","name":"Nancy"},{"uid":"2","name":"Moore"}],
});
}
// update user following
handleFollowUser(user_id) {
const uid_data = { user_id: user_id };
axios
.get("http://localhost/data.json", { uid_data })
.then(response => {
this.setState(state => ({
//data: newData,
result_data: response.data[0].status
}));
alert(result_data);
})
.catch(error => {
console.log(error);
});
}
// update user unfollowing
handleUnFollowUser(user_id) {
const uid_data = { user_id: user_id };
axios
.get("http://localhost/data.json", { uid_data })
.then(response => {
this.setState(state => ({
//data: newData,
result_data: response.data[0].status
}));
alert(result_data);
})
.catch(error => {
console.log(error);
});
}
render() {
return (
<span>
<label>
<ul>
<h1>Users</h1> <br />
{this.state.result_data }
{this.state.data.map((users) => {
return (
<div key={users.uid}>
<div>
<b> Name: </b>{users.name}
<br />
{this.state.result_data === ''
? <span onClick={() =>
this.handleFollowUser(users.uid)}>Follow</span>
: <span onClick={() =>
this.handleUnFollowUser(users.uid)}>unfollow</span>
}
</div>
</div>
)
}
)}
</ul>
</label>
</span>
);
}
}
This is what solved my Reactjs problem.I first initialize
isToggleOn: !state.isToggleOn in the click event and in the constructor I implemented
this.state = {isToggleOn: true};
My click button becomes
<button onClick={this.handleFollowUser}>
{this.state.isToggleOn ? 'Follow' : 'Unfollow'}
</button>

Resources