Update immediately after delete data - reactjs

class App extends Component{
constructor(props){
super(props);
this.state = {
users:[]
};
}
componentDidMount() {
axios.get(`http://localhost:3000/employees`)
.then(res => {
const users = res.data;
this.setState({ users });
})
}
render(){
return(
<div>
<Main users= {this.state.users}/>
<Form/>
</div>
);
}
}
class Main extends Component{
state = {
id: ''
}
handleChange = event => {
this.setState({ id: event.target.value });
}
handleSubmit = event => {
event.preventDefault();
axios.delete(`http://localhost:3000/employees/${this.state.id}`)
.then(res => {
console.log(res);
console.log("this is" + res.data);
})
}
render(){
return(
<div>
<form onSubmit={this.handleSubmit}>
<label>
Person Name:
<input type="text" name="name" onChange={this.handleChange} />
</label>
<button type="submit">Delete</button>
</form>
</div>
)
}
}
Can someone tell me why after the Axios delete request, how I can render the new state in the users array from App component?
In the App component, I am trying to make this.state.users as a props to send it to the Form component. My guess is put this.setState({users: res.data}). The delete request is fine with 200, but I need to refresh the page to get the new result. How can I update immediatly?
// this is a json object
"employees": [
{
"id": 8,
"first_name": "Lteve",
"last_name": "Palmer",
"email": "steve#codingthesmartway.com"
},

As Dave mentions in a comment you want to have single responsibility for state between components.
This topic is also discussed in the blog post You Probably Don't Need Derived State, where one solution to your problem is to have Main "report back" to App in order to update state. So either App passes down a onDeleteUser function, or a callback for when a user is removed, such as onUserWasDeleted.
The latter can be done with the least amount of changes to your code I suppose.
class App extends Component {
constructor(props) {
super(props);
this.onUserWasDeleted = this.onUserWasDeleted.bind(this);
}
onUserWasDeleted(userId) {
// remove user that was successfully removed
this.setState({ users: this.state.users.filter(user => user.id !== userId) });
}
render() {
return (
<Main
users={this.state.users}
// pass down a callback to Main
onUserDeleted={this.onUserWasDeleted}
/>
);
}
}
class Main extends Component {
handleSubmit = event => {
event.preventDefault();
axios.delete(`http://localhost:3000/employees/${this.state.id}`)
.then(res => {
console.log(res);
console.log("this is" + res.data);
// call our callback function in App
this.props.onUserWasDeleted(this.state.id);
})
}
}

Related

React How to Get Value of a SelectList from a Child Component

I'm working on a form in which one of the input items will be a select list. I am trying to implement the form by using reusable components. The hierarchy is this:
<SignupForm>
<CountrySelectList>
<SelectList>
I am able to get the select list to render, but I am not sure how to pass the needed props to the child componets in order to bind the selection from the drop down list? Here are the components (with some form fields removed for brevity).
SignupForm
class SignupForm extends React.Component {
constructor(props) {
super(props);
this.state = {
country: ''
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({
[event.target.name]: event.target.value
});
}
handleSubmit(event) {
event.preventDefault();
let formData = new FormData();
formData.append("Country", this.state.country);;
fetch("api/account/signup", {
method: "POST",
body: formData })
.then(response => response.json())
.then(data => console.log(data));
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<CountrySelectList name="country" value={this.state.country} onChange={this.handleChange} />
<SubmitButton btnText="Submit"/>
</form>
);
}
}
export default SignupForm;
CountrySelectList
class CountrySelectList extends React.Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
data: []
};
}
async componentDidMount() {
try {
const response = await fetch('api/location/countries');
const json = await response.json();
this.setState({
data: json,
isLoaded: true
});
} catch (error) {
console.log(error);
}
}
render() {
const { error, isLoaded, data } = this.state;
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <LoadingSpinner/>;
} else {
return (
<SelectListGroup>
<Label label="country" title="Country"/>
<SelectList data={data} value={this.props.value} onChange={this.props.onChange} />
</SelectListGroup>
);
}
}
}
export default CountrySelectList;
SelectList
export default function SelectList({ data, value, onChange, addClass="" }) {
return (
<select
value={value}
className={`form-select ${addClass}`}
onChange={onChange}>
{data.map((data, index) => (
<option key={index} value={data.value}>
{data.text}
</option>
))}
</select>
);
}
This answer has a good explanation of getting data from forms and their component parts.
Get form data in ReactJS
You're kind of doing the same job twice by saving the selection as state and using form -> submit.
If you give your select a name attribute, then in the handleSubmit function, the event parameter will contain a named element that you can get the value from.
<select name='country' ...
Then the function can use
const handleSubmit = (event) => {
console.log(event.target.country.value);
}
The onSubmit event of the form carries all the info, you just have to name the elements.

Can't render {this.state.city}

I'm creating a weather app in React using OpenWeatherMap API. There are input form and a button, and I'm expecting to see city name when I click the botton. I received data from the API when I do so, but can't render it on a screen while I can log it in a console.
For this, I'm using three separated files. App.js, Form.js for submitting terms, and weather.js for API configuration.
I'm guessing that I need to map the received data but not yet successful.
class App extends React.Component {
state = {
city: null,
}
getWeather = async city => {
const response = await weather.get('/forecast', {
params: {
q: city
}
});
this.setState({
city: response.name,
})
console.log(city); <--- This works
}
render() {
return (
<div>
<Form loadWeather={this.getWeather} />
<p>{this.state.city}</p> <--- This doesn't work
</div>
);
}
}
class Form extends React.Component {
state = { term: '' };
onFormSubmit = (event) => {
event.preventDefault();
this.props.loadWeather(this.state.term);
this.refs.textInput.value = '';
}
render() {
return (
<div>
<form onSubmit={this.onFormSubmit}>
<input
ref="textInput"
type="text"
value={this.state.term}
onChange={event => this.setState({term: event.target.value})}
/>
<button>Get Weather</button>
</form>
</div>
);
}
}
export default Form;
I'm going to pass the {this.state.name} as a prop to a child component, but so far the received data doesn't even appear on that component ifself.
"this.setState" is a function, should be called like this.
this.setState({
city: response.name,
})
You're setting state's city to response.name. You tagged the question as axios, so I'm assuming you're using axios for the ajax. If so, you'll want to get the data from the response back from response.data.name, not response.name.
If you want to get the city details from the initial render try calling/invoking the getWeather() method in componentDidMount() life cycle. The way you are using setState() is also wrong. it should be something like this as mentioned below. The same lifecyle could be used to get the data even though you have used a separate file to invoke the method
class App extends React.Component {
state = {
city: null
};
componentDidMount() {
this.getWeather("city");
}
getWeather = async city => {
const response = await weather.get("/forecast", {
params: {
q: city
}
});
this.setState({
city: response.name
});
};
render() {
return (
<div>
<p>{this.state.city}</p>
</div>
);
}
}

How to update component based on container's state change

I have a React container called UserContainer which renders a component called UserComponent.
The code looks approximately like this (I have removed the unnecessary bits):
// **** CONTAINER **** //
class UserContainer extends React.Component<ContainerProps, ContainerState> {
state = { firstName: "placeholder" };
async componentDidMount() {
const response = await this.props.callUserApi();
if (response.ok) {
const content: ContainerState = await response.json();
this.setState({ firstName: content.firstName });
}
}
private isChanged(componentState: ComponentState) {
return this.state.firstName === componentState.firstName;
}
async save(newValues: ComponentState) {
if (!this.isChanged(newValues)) {
console.log("No changes detected.");
return;
}
const response = await this.props.changeFirstName(newValues.firstName);
if (response.ok) {
const content: ContainerState = await response.json();
this.setState({ firstName: content.firstName });
}
}
render() {
return <UserComponent firstName={this.state.firstName} onSave={(newValues: ComponentState) => this.save(newValues)} />;
}
}
export default UserContainer;
// **** COMPONENT **** //
class UserComponent extends React.PureComponent<ComponentProps, ComponentState> {
constructor(props: ComponentProps) {
super(props);
this.state = { firstName: props.firstName }
}
render() {
return (
<div>
<input type="text" value={this.state.firstName} onChange={evt => this.setState({ firstName: evt.target.value})} />
<button type="button" onClick={() => this.props.onSave(this.state)}>Save</button>
</div>
);
}
}
export default UserComponent;
The problem is that this.state.firstName in the component is always "placeholder". Even after the container gets its values from the API, the state of the component is not changed (however, the props are changed). When adding console.log into the individual methods, the flow of individual steps is following:
Container render()
Component constructor()
Component render()
Container didMount()
Container render()
Component render()
As you can see, the component constructor is called just once, prior to the container receiving its data from the backend API. Is there a way to pass the updated container state into the component in order to display the real data?
There are really FEW cases where updating state by props is necessary, I suggest you to read the full blog post from facebook under paragraph "Preferred Solutions": https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html
class UserComponent extends React.PureComponent<ComponentProps, ComponentState> {
constructor(props: ComponentProps) {
super(props);
this.state = { firstName: props.firstName }
}
componentWillReceiveProps(nextProps: ComponentProps){
if(nextProps.firstName != this.props.firstName){
this.state = { firstName: nextProps.firstName }
}
}
render() {
return (
<div>
<input type="text" value={this.state.firstName} onChange={evt => this.setState({ firstName: evt.target.value})} />
<button type="button" onClick={() => this.props.onSave(this.state)}>Save</button>
</div>
);
}
}
For latest React version please use getDerivedStateFromProps
You are already passing the updated data to the component. Only mistake is, you are assigning it once. So, whenever you get the updated values, it doesn't reflect, since you don't have only assigned it once.
Two ways to go about it.
If there is no manipulation taking place. Change this.state.firstName to this.props.firstName
<input type="text" value={this.props.firstName} onChange={evt => this.setState({ firstName: evt.target.value})} />
If there is some manipulation taking place, you'll be doing it in the componentWillReceiveProps method and then setting your firstName state. This method will be triggered whenever you'll be updating the states.
Example -
componentWillReceiveProps(nextProps) {
if(this.props.firstName!==nextProps.firstName) {
//do your validation
}
}
EDIT
As dubes rightly pointed out, componentWillReceiveProps method is deprecated. So you'll have to use the static getDerivedStateFromProps and have to return the new resulting state from this method.
Hope this helps :)

Correct way to React axios post with UUID and nested object in JSON payload?

My server-side application accepts an int, does some simple math, and returns an int as Content-Type application/json. The api has been tested with Postman and works correctly.
I'm looking for the proper way to handle an Axios POST with a JSON payload that includes a UUID with an object nested below it. As suggested, I added [''] around the UUID to play nicely with React. If I click 'Post' without entering a value my server returns an int for 'current_value'. If I enter a number in the field 'current_value' returns a string e.g., 4 + 2 = "42".
import React, { Component } from 'react';
import axios from 'axios';
class Post extends Component {
constructor() {
super();
this.state = {
current_value: 0
};
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange = event => {
this.setState({ current_value: event.target.value });
console.log(event.target.value);
};
handleSubmit = event => {
event.preventDefault();
axios.post('http://my.server.url', {
foo: 'bar',
['e0ea641b-3de4-4a76-857d-11da9352698a']: { current_value: this.state.current_value }
})
.then(response => {
this.setState({ current_value: response.data.current_value });
console.log(JSON.stringify(response.data));
});
};
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<label>Input Number:
<input type="number" name="current_value" onChange={this.handleChange} />
</label>
<button type="submit">Post</button>
</form>
<div>
Output Number: { this.state.current_value }
</div>
</div>
);
}
}
export default Post;
Try to escape your uuid like below, it should work:
{
foo: 'bar',
['e0ea641b-3de4-4a76-857d-11da9352698a']:{ current_value: this.state.current_value }
}
With a nod to help from #GuilhermeLemmi, I've landed on the answer that addresses both my initial issue and the problem of handling the response where the item in question contains a minus sign -. Wrapping my UUID in [] in the data object wasn't necessary, but I did need to wrap it in single quotes. On the return side I did need to wrap the response in [''] but leave it as an object, don't JSON.stringify() it. Now everything flows nice and smooth.
import React, { Component } from 'react';
import axios from 'axios';
class Post extends Component {
constructor() {
super();
this.state = {
current_value: 0
};
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange = event => {
this.setState({ current_value: JSON.parse(event.target.value)});
console.log(event.target.value);
};
handleSubmit = event => {
event.preventDefault();
const data = {
foo: 'bar',
'e0ea641b-3de4-4a76-857d-11da9352698a': {
current_value: this.state.current_value
}
};
console.log(data);
axios.post('http://my.server.url', data)
.then(response => {
const obj = response.data;
this.setState({ current_value: obj['e0ea641b-3de4-4a76-857d-11da9352698a'].current_value });
console.log(obj['e0ea641b-3de4-4a76-857d-11da9352698a'].current_value);
})
.catch((error) => {
console.log(error);
});
};
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<label>Input Number:
<input type="number" name="current_value" onChange={this.handleChange} />
</label>
<button type="submit">Post</button>
</form>
<div>
Output Number: { this.state.current_value }
</div>
</div>
);
}
}
export default Post;

Setting State Array and Appending Value on Update

I'm still learning about state and lifecycle with ReactJS and have run into a scenario where I have a form that on submit should save the form value and then append the returned JSON object to the end of an array which would re-render the component storing the original array.
With my current setup, I have the components setup and form submit with returned JSON object, but the state contains an empty array rather than the object spread {...comment} and it doesn't look like the setState is updating component, but that could be due to the empty array mentioned before. Can anyone point me in the right direction?
Comment:
import React from 'react';
import fetch from 'node-fetch';
//record Comment - Comment Form Handle POST
class CommentForm extends React.Component {
constructor(props){
super(props);
this.state = {
value: '',
comments: []
};
this.onChange = this.onChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
postComment(comment, recordId, csrfToken) {
var body = { comment: comment };
var route = 'http://localhost:3000/record/' + recordId + '/comment';
fetch(route,
{
method: 'POST',
body: JSON.stringify(body),
headers: {
'X-CSRF-Token': csrfToken,
'Accept': 'application/json',
'Content-Type': 'application/json'
}
})
.then(res => {
return res.json();
})
.then(data => {
console.log(data);
let commentsArr = this.state.comments;
this.setState({comments: commentsArr.concat(data)});
})
.catch(err => {
console.log(err);
});
}
onChange(e){
this.setState({
value: e.target.value
});
}
handleSubmit(e){
e.preventDefault();
this.postComment(this.state.value, this.props.recordId, this.props.csrf);
}
render(){
return (
<div className="record-comment__form">
<div className="row">
<form action={"/record/" + this.props.recordId + "/comment"} method="post" onSubmit={this.handleSubmit}>
<input type="hidden" name="_csrf" value={this.props.csrf}/>
<textarea name="comment" className="record-comment__form-text-area" onChange={e => this.setState({ value: e.target.value })} value={this.state.value}></textarea>
<button type="submit" className="record-comment__form-button" disabled={!this.state.value}>Comment</button>
</form>
</div>
</div>
)
}
}
//record Comment - Comment
const Comment = props => {
return (
<div className="row">
<div className="col-md-12">
<h5>{props.user_id}</h5>
<h4>{props.comment}</h4>
<h3>{props.synotate_user.fullNameSlug}</h3>
</div>
</div>
)
}
//record Comment - Container
export default class Comments extends React.Component {
render() {
return (
<div className="record-comment-container">
<CommentForm recordId={this.props.recordId} csrf={this.props.csrf}/>
{ this.props.record_comments.map((comment, i) =>
<Comment {...comment} key={this.props.recordCommentId}/>
)}
</div>
);
}
}
Record (Parent component)(Where Comment is being set):
//GET /api/test and set to state
class RecordFeedContainer extends React.Component{
constructor(props, context) {
super(props, context);
this.state = this.context.data || window.__INITIAL_STATE__ || {records: []};
}
fetchList() {
fetch('http://localhost:3000/api/test')
.then(res => {
return res.json();
})
.then(data => {
console.log(data);
this.setState({ records: data.record, user: data.user, csrf: data.csrfToken });
})
.catch(err => {
console.log(err);
});
}
componentDidMount() {
this.fetchList();
}
render() {
return (
<div className="container">
<h2>Comments List</h2>
<RecordFeed {...this.state} />
</div>
)
}
};
//Loop through JSON and create Record and Comment Container Component
const RecordFeed = props => {
return (
<div>
{
props.records.map((record, index) => {
return (
<div className="row">
<div className="col-md-6 col-md-offset-3 record-card">
<RecordCard {...record} key={record.recordIdHash} user={props.user} />
<Comments {...record} key={index} recordId={record.recordIdHash} csrf={props.csrf}/>
</div>
</div>
);
})
}
</div>
)
}
Your problem is that when rendering <Comments>, the this.props.record_comments is not the comments you've updated in the state of the <CommentForm> component. Each component has it's own internal state.
You need to pass the state along to your <Comments> component. You will need to move your state up to the top level or use a state management system like Redux which will allow you to access a shared state which could contain your comments array.
From the top level component you could manage the state there, like so:
this.state = {
comments: [],
// other shared state
};
You can pass along an update comments function, named for example updateCommentsFunc() to <CommentForm> like so:
<CommentForm updateComments={this.updateCommentsFunc} recordId={this.props.recordId} csrf={this.props.csrf}/>
Which will allow you to pass the updated comments back up to the parent component via something like:
const updateCommentsFunc = (newComments) => {
this.setState({comments: [...this.state.comments, newComments]});
}
Your postComment() function doesn't appear to be properly bound to your enveloping <CommentForm/> component's this. As a result; calling this.setState() from within the function isn't really doing anything.
Try binding it within your constructor method.
constructor(props) {
// ...
this.postComment = this.postComment.bind(this)
}
Or by declaring it using an arrow function.
postComment = (comment, recordId, csrfToken) => {
// ...
}
See this article for more info on React binding patterns.

Resources