How to update state of parent from child component in reactJS - reactjs

class A extends Component {
constructor(props) {
super(props);
this.state = {
fruitsDetailsList: [],
fruitCode: this.props.fruitCode,
};
}
showModal = () => {
this.setState({ show: true });
};
hideModal = () => {
this.setState({ show: false });
};
componentDidMount() {
const url = 'http://localhost:3000/getFruitslist';
fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
fruitCode: this.state.fruitCode,
}),
})
.then(res => {
if (res.ok) {
return res.json();
}
throw new Error(res.status);
})
.then(res => {
this.setState({
fruitsDetailsList: res.fruitDetailsList,
});
})
.catch(error => {});
}
render() {
const columns = [
{
Header: 'Sr.No',
id: "row",
maxWidth: 50,
Cell: (row) => {
return <div>{row.index + 1}</div>
}
},
{
Header: 'Actions',
id: 'FruitName',
accessor: d => (
<div>
<B/>
</div>
)
}
];
return (
<div>
<ReactTable
className="-striped -highlight"
columns={columns}
data={this.state.fruitsDetailsList}
defaultPageSize={10}
noDataText={'No Data available.'}
/>
<p></p>
</div>
);
}
Class B extends component{
constructor(props) {
super(props);
this.state = { modal: false };
this.toggle = this.toggle.bind(this);
}
toggle() {
this.setState({
modal: !this.state.modal
});
}
render() {
return (
<div>
<Button onClick={this.toggle}/>
<Modal isOpen={this.state.modal}>
<ModalHeader>Fruits list</ModalHeader>
<ModalBody>
<Formik
initialValues={{fruitName: ''}}
onSubmit={(fields, action) => {
action.setSubmitting(true);
const url = 'http://localhost:3000/getFruit';
fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
fruitName: fields.fruitName,
}),
})
.then(res => {
action.setSubmitting(false);
console.log("Success!!!);
})
.catch(error => {});
}}
render={({ errors, touched, isSubmitting }) => (
!isSubmitting ? (
<Form>
<div className="form-group">
<label htmlFor="fruitName">FruitName</label>
<Field name="fruitName" type="text"/>
</div>
<div className="form-group">
<Button type="submit" className="bg-gradient-theme-left border-0 " size="m">Submit</Button>
</div>
</Form>
)
)}
/>
</ModalBody>
</Modal>
</div>
);
}
}
- REACT JS
As you can see there are 2 classes
1)Component A
2)Component B
In Component A I am calling B component as Button in react-table
Actually we have to render react table with all the data in database by calling post API request '/getFruitslist' which we are calling in componentDidMount of Component A so that data in react-table gets populated correctly in table
Now when we click on button of Component B one record gets inserted in
database but as API is called in ComponentDidMount of Component A which is
parent of Component B , data does not populate in react-table on clicking button of B component . How to achieve this ?

The philosophy of React is to manage the state into the top component. A child component should not keep a reference to its parent in order to update the parent state.
To achieve this behavior, the parent component should pass a callback to the child component through its properties, then the child component can invoke this callback by calling the matching property
In your example, you can pass a property onAddFruit to the B component. When the POST call succeed, you should call this function
.then(fruit => {
action.setSubmitting(false);
console.log("Success!!!);
this.props.onAddFruit(fruit);
})
In the parent component A, you define a function which will add a fruit to the state variable fruitsDetailsList. Then you pass this function to the component B through the property onAddFruit
handleAddFruit = fruit => {
this.setState(prevState => ({
fruitsDetailsList: [...prevState.fruitsDetailsList, fruit]
});
};
The declaration of the B element will be like this :
<B onAddFruit={this.handleAddFruit}/>

There are ways to achieve what you are looking for. The easiest one is to pass down through props the necessary objects, values. In complex cases I would recommend to use redux for application state management, it is way more easier to keep your state consistent in a case when you need to have the state objects accessible in several components.
Pass down the props:
Once you have 2 components, data can be passed down for example from A to B. Sharing below 1 small example how you can achieve that:
class A extends Component {
constructor(props) {
super(props);
this.state = {
fruitsDetailsList: [],
fruitCode: this.props.fruitCode,
};
}
render {
return (
<B dataFromA={this.state.fruitsDetailsList} />
)
}
}
Redux option:
So what is Redux?
A predictable state container for JavaScript apps.
Basically your application will have the state object in 1 common place what you can match to each components if it needs to be accessed with mapStateToProps function from redux. The link Connect: Extracting Data with mapStateToProps explains how to that in more details. Once you need to modify any of the elements you can use mapDispatchToProps to do so. Please find the Connect: Dispatching Actions with mapDispatchToProps link for further setup.
Read further about the setup, getting started here: Redux
Summary:
In your scenario because of API calls, possible frequent update in state objects I recommend to go with the second option. It might be complicated at the first look but it is worth to effort because you don't need to worry about the state consistency and through props the data is updated automatically once you have modification on any of the objects.
I hope this helps!

Related

Update immediately after delete data

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);
})
}
}

What is the correct way to call API inside a Modal, when it's visible in React?

Suppose that I have a Modal inside a Component and I want to show the Modal when some buttons are clicked:
render(){
...
<Modal
is={this.state.productId}
visilble={this.state.visible}
/>
}
Inside the Modal Component, I want to call API to get the product detail based on the selected id as the following:
componentWillReceiveProps(nextProps) {
if(nextProps.visible && !this.props.visible) {
fetch(...).then(res => {
this.setState({
product: res.data
})
}).catch(err => {
...
})
}
}
From the React docs it says that componentWillReceiveProps, componentWillUpdate is deprecated and you should avoid them in new code.So I try use static getDerivedStateFromProps()
static getDerivedStateFromProps()(nextProps) {
if(nextProps.visible && ...) {
fetch(...).then(res => {
return {
product: res.data
}
}).catch(err => {
...
})
} else return null (or just return null here without else)
}
The above code doesn't work since fetch is asynchronous so it always returns null or doesn't return anything, you can't use await here to wait for the api to resolve also, and I heard that getDerivedStateFromProps shouldn't use for data fetching.
So what is the best way solve the problem ?
I think it's better to decide whether to show Modal component or not in parent component as Modal component should be a functional component to render only modal related view. This way every time Modal component will not be rendered and only rendered when visible flag is true.
{ this.state.visible &&
<Modal />
}
In parent component you could fetch data in componentDidMount if just after initial render the data is required or componentDidUpdate if after every update the fetch data is required for modal. After fetching data set state of visible to true.
Happy Coding!!!
You can mount the Modal based on this.state.visible and start fetching when Modal is mounted on componentDidMount or when the props are changing through componentDidUpdate
// delay
const delay = () => new Promise(res => setTimeout(res, 1000));
// fake products
const products = [
{ id: 1, text: "product 1" },
{ id: 2, text: "product 2" },
{ id: 3, text: "product 3" }
];
// fake ajax call
const API = async productId => {
await delay();
return products.find(p => p.id === productId);
};
class Parent extends Component {
state = { productId: 1, visible: false };
toggleShow = () => {
this.setState(prevState => ({ visible: !prevState.visible }));
};
setProductId = productId => this.setState({ productId, visible: true });
render() {
return (
<div className="App">
<button onClick={this.toggleShow}>show or hide</button>
<button onClick={() => this.setProductId(1)}>fetch product 1</button>
<button onClick={() => this.setProductId(2)}>fetch product 2</button>
<button onClick={() => this.setProductId(3)}>fetch product 3</button>
<button onClick={() => this.setProductId(4)}>unknown product id</button>
{this.state.visible && <Modal is={this.state.productId} />}
</div>
);
}
}
class Modal extends Component {
state = { product: null, fetching: false };
componentDidMount = () => {
this.fetchProduct();
};
componentDidUpdate = prevProps => {
if (prevProps.is !== this.props.is) {
this.fetchProduct();
}
};
fetchProduct = async () => {
this.setState({ fetching: true });
const product = await API(this.props.is);
this.setState({ product, fetching: false });
};
render() {
const { product, fetching } = this.state;
if (fetching) return <h1>{`fetching product ${this.props.is}`}</h1>;
return product ? (
<div>
<h1>{`product id: ${product.id}`}</h1>
<h3>{`text: ${product.text}`}</h3>
</div>
) : (
<h1>Product not found</h1>
);
}
}
SandBox
Actually in another approach, you can use <modal/> in another pureComponent
(for example: componentContainer) and just call it in your main view. and use just one object inside your main view state as your <componentContainer/> data scope and give it as a property like this code
construcor(prop){
super(props);
this.state={
productDetail:null<<change this in data fetch
}
}
<componentContainer data={this.state.productDetail} modalVisible={this.state.changeNumderModal}/>
and inside your component container:
<Modal
animationType="fade"
transparent={true}
visible={this.props.modalVisible}//<<<<<<<<<
onRequestClose={() => {
}}>
//contents:
this.props.data.map(()=>//-----)
</View>
</Modal>
in this method you have just one modal and one scope as its data, you can call the fetchs and other functions in your main component as others... and if its so needed you can even pass a function as property to modal container to :
someFunction(someval){
//do some thing
}
<componentContainer data={this.state.productDetail} modalVisible={this.state.changeNumderModal} someFunction={(val)=>this.someFunction(val)}/>
and call it inside :
this.props.someFunction('what ever you want')
So Let's assume you did that hide and show of modal with button click, now whenever the model will open
inside the componentWillMount function
class Modal extends Component {
this.state = {product:[], loader : false}
componentWillMount(){
fetch(...).then(res => {
this.setState({
product: res.data
})
}).catch(err => {
...
})
}
render(){
const {product, loader} = this.state;
return (
if (loader) return <ProductComponent data = {product}/>
else return <div> No data found </div>
);
}
}
You can use the approach below:
componentDidUpdate(prevProps, prevState, snapshot) {
if (this.props.visible && !prevProps.visible) {
// make the API call here
}
}

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 :)

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.

displaying data from fetch api using react

I am developing a simple website which displays data from an API(JSON) into a page using react.
I'm using the fetch() API.
I am able to get the data from the API and set it into the 'App' component state but I'm not able to pass is down to Table and Row components that I've manually created.
class App extends React.Component {
constructor (props) {
super(props)
this.state = {ticker: {}, volume: {}}
this.loadData = this.loadData.bind(this)
this.loadData()
}
loadData () {
fetch(ticker)
.then((resp) => resp.json())
.then((data) => {
this.setState({
ticker: data
})
})
.catch((err) => {
console.log(err)
})
fetch(volume)
.then((resp) => resp.json())
.then((data) => {
this.setState({
volume: data
})
})
.catch((err) => {
console.log(err)
})
}
render () {
return (
<div>
<Navbar />
<div className='container'>
<div className='align'>
<div className='element' />
</div>
<Table volume={this.state.volume} ticker={this.state.ticker} />
</div>
</div>
)
}
}
BOTTOM LINE:
I have an API with data, and I have 3 components, Table, which also has a row component.
I want to display variables in the Row component
which looks like this
<Row img='eth' name='Ethereum' price='' volume='' change='' marketCap='' />
You constructor :
constructor (props) {
super(props);
this.state = {ticker: {}, volume: {}}
this.loadData = this.loadData.bind(this);
}
For fetching data you need always to use lifecycle component, as componentDidMount or componentWillMount, thus :
componentDidMount(){
this.loadData()
}
And then in your state you will have the data.
In your render method pass it as props to the Table component:
render(){
return(
<Table volume={this.state.volume} ticker={this.state.ticker} />
)
}
Then in from the Table component pass it to Row component as props, thus :
render(){
return(
<Row img='eth' name='Ethereum' price='' volume={this.props.volume} change='' marketCap='' />
)
}
If you have array of objects, something like :
this.state = {
volume: [ {name: "One", size: 1 }, {name: "Two", size: 2 }, ..... ]
}
You'll need to loop through the array and show the Row component for each object.
Thus, your Table component should be something as follows:
render(){
return (
<div>{this.props.volume.map(vol => <Row img='eth' name='Ethereum' price='' volume={vol} change='' marketCap='' />) }</div>
)
}
If you make the ajax call in componentDidMount, then React will rerender when the state changes (https://facebook.github.io/react/docs/react-component.html#componentdidmount). But you still have to anticipate that the volume and ticker props will be empty until the request resolves and React rerenders.

Resources