React.js setState does not re-render child component - reactjs

I am passing a list from the parent component's state to a child component's <CheckboxList /> props. The list should then show in the child component. After fetching the list elements from the database I use setState to update the list but the child component is not re-rendered with the new list.
class ParentComponent extends Component {
state = {
users: [{ username: 'before' }]
}
componentDidMount() {
const result = [];
db.collection("users").get().then((querySnapshot) => {
querySnapshot.forEach(function (doc) {
let user = doc.data();
result.push(user);
});
}).then(() => {
this.setState({
users: result
})
})
}
render() {
return (
<div>
<h1>List of users</h1>
<CheckboxList list={this.state.users} />
</div>
)
}
}
The list shows before and it does not update with content from the database. I checked and the values are fetched from the database, they are just not passed to the <CheckboxList /> after the setState. Can anybody help me?

The problem was that I was retrieving the list from the CheckboxList status and then using it through that. Now I have fixed it. I am retrieving it in the CheckboxList render(), storing it into a variable and then using it from there.

Related

Send Data From Parent Component To Child Component In React

I am new to react & need some help. I am getting data from a REST API using Axios. I have two Components. A Parent & Child Component. In parent Component I am fetching the Summarised data from API having multiple records while the Child component is used to make another API call for Details of the record when user clicks on a specific record in the Parent Component.
The Parent Component has 3 attribute ( Document-Number, document-Type & Approver ). I need to pass the "Doc-Number" & " Doc-Type" values to the child Component API URl when user clicks on the button.
Note: I donot have any dedicated ID attribute in the Parent API response and that's the reason I am using index as a key.
Here is My Parent Component
import React, { Component } from "react";
import axios from "axios";
import Getdetails from "./Getdetails";
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
records: [],
errorMessage: "",
};
}
componentDidMount() {
axios
.get( "http://www.example.Api.com/generalInfo&limit=10&offset=2" )
.then((res) => {
this.setState({ records: res.data });
console.log(res);
})
}
render() {
const { records } = this.state;
return (
<div>
<ul>
{records.map((record, index) => (
<li key={index}>
Document Number : {record.Number}
Document Type: {record.documentType}
Approver : {record.approver}
//I Want to send the "document Number & documentType" to Childdetails component Url when user click on this button.
<button onClick={{record.Number} & {record.documentType}}>Get Details</button>
</li>
))}
</ul>
</div>
)
}
}
export default Parent;
Here is My Child Component
import React, { Component } from "react";
import axios from "axios";
import Parent from "Parent";
class ChildDetails extends Component {
constructor(props) {
super(props);
this.state = {
getdetails: [],
errorMessage: "",
};
}
componentDidMount() {
axios
.get("http://www.example-Details-API.com/documentType={record.documentType}/id={record.Number}")
.then((res) => {
this.setState({ getdetails: res.data });
console.log(res);
})
}
render() {
const { getdetails } = this.state;
return (
<div>
<h1>Get Details</h1>
<ul>
<li> Number : {getdetails.Number} </li>
<li> Title : {getdetails.Title} </li>
<li> Submit Date : {getdetails.Date} </li>
<li> Site : {getdetails.site} </li>
<li> Requester : {getdetails.requesterName}</li>
<li> document Type : {getdetails.documentType}</li>
</ul>
</div>
)
}
}
export default ChildDetails
Thanks To everyone and Your help is really appreciated.
When you talk about Parent and Child components I expect to see the Child rendered by the Parent, I am not sure if this is your case. Anyway, the main way to pass data from parents to childs are via the props. Applied to your example:
In the parent's render function:
<ChildDetails record={record} />
In the child's render function:
componentDidMount() {
axios
.get(`http://www.example-Details-API.com/documentType=${props.record.documentType}/id=${props.record.Number}`)
.then((res) => {
this.setState({ getdetails: res.data });
console.log(res);
})
}
See that in the child the data is accessed via props.record.
If your ChildDetails is not rendered by the Parent, then you need to pass the information to upper levels through callbacks.
Passing data as a prop to child component
const onClickHandler = (record,document) => {
return (
<ChildDetails recordNumber={record} documentType={document}/>
)
};
Passing data as parameters to event handler
<button onClick={onClickHanlder(record.Number,record.documentType)}>Get Details</button>
If you wannna use index you can use it as a third argument
You can add two more state values in your parent component such as:
this.state = {
records: [],
errorMessage: "",
Selected-Doc-Number: ""
Selected-Doc-Type: ""
};
Now on you can set these state values (Selected-Doc-Number, Selected-Doc-Type) on the click of record button on parent component as:
const selectRecordForChildComponent = (selectedDocNumber, selectedDocType) => {
this.setState({Selected-Doc-Number: selectedDocNumber,
Selected-Doc-Type: selectedDocType})
}
<button
onClick={() => {selectRecordForChildComponent(record.Number, record.documentType)}}>
Get Details
</button>
Now on you can pass these values (Selected-Doc-Number, Selected-Doc-Type) to child component using props as from the Parent-component:
<ChildDetails
selectDocNumber = {this.state.Selected-Doc-Number}
selectedDocType = {this.state.Selected-Doc-Type} />
Now you can access these passed props in <ChildDetails> component using it's props as for example:
componentDidMount() {
const docNumber = this.props.selectDocNumber
const docType = this.props.selectedDocType
axios
.get(`http://www.example-Details-API.com/documentType=${docType}/id=${docNumber}`)
.then((res) => {
this.setState({ getdetails: res.data });
console.log(res);
})
}
Hope this may help...
In the parent component onClick create a function that will be called and return the props to the child component.
<button onClick={() => this.handleClick(record.number, record.documentType)}>Get Details</button>
And the handle click function should be like that
handleClick(num, type) {
return (
<Child recordNum={num} docType={type}></Child>
)
};
Don't forget to bind the function in the constructor. You can then call the external API in the did mount function in the child and replace the url with the required props from parent like in the above example this.props.recordNum and this.props.docType.

How to update the props of a component in a list of components

I'm trying to update a prop value of a component in a list of components. Following is an example of it.
I'm developing an app using ReactNative
...
constructor(props) {
state = {
components: [*list of components*],
}
componentDidMount() {
fetchingAPI().then(response => {
const components = [];
for (const data of response.data) {
components.push(<MyComponent numOfLike={data.numOfLike} />);
}
this.setState({components});
});
}
render() {
return (
...
{this.state.components}
...
);
}
When I want to update a component, I update the whole state named components like :
updateAComponent(index, newNumOfLike) {
const components = this.state.components;
components[index] = <MyComponent numOfLike={newNumOfLike} />
this.setState({components});
}
But, this method change the component, not update. right? I means the components state is updated but MyComponent in components[index] is changed.
So, if I want to update the MyComponent in components[index] using the way of update the props numOfLike directly, how can I do it?
addition :
What I did not mention is that the MyComponent has a Image tag in it. So if I use FlatList or array.prototype.map there are several issues.
If I update the state, the whole list will be re-rendered. So if there are many list item, the speed of updating is very slow.
Since there are Image tag in the list, if I update a list item, the whole Image tags blink since the list items are re-rendered.
In this situation
Is there way to re-render(update) only a component which I want to update? (target updating)
If there in no way to target updating, just let the whole list items(components) re-rendered when just a component is updated?
You can use setNativeProps, described in the direct manipulation documentation
components[index].setNativeProps(propsObj)
You can modify your componentDidMount function like this (so that there are no race around or async conditions in the code) -:
componentDidMount() {
fetchingAPI().then(response => {
this.setState({
components: this.state.components.concat(
response.data.map(i => <MyComponent numOfLike={i.numOfLike} />)
)});
});
}
Can you try with the FlatList?
eg:
...
constructor(props) {
state = {
componentsData: [],
}
componentDidMount() {
fetchingAPI().then(response => {
this.setState({componentsData: response.data});
});
}
_renderItems = ({ item, index }) => {
return(
<MyComponent numOfLike={item. numOfLike} />
)
}
render() {
return (
...
<FlatList
data={this.state.componentsData}
renderItem={this._renderItems}
keyExtractor={(item, index) => index.toString()}
extraData={this.state}
/>
...
);
}
Then when you want to update the list,
updateAComponent(index, newNumOfLike) {
const data = this.state.componentsData;
data[index].numOfLike = newNumOfLike
this.setState({componentsData: data});
}

React doesn't re-render component on state change

I have a component with a search field and a list of items, where I am using that value from search field to filter through the array of items and update the list. This is are the parts of the component responsible for that:
this.state = {
tasks: null,
unfilteredTasks: null,
};
componentDidMount() {
this.gateway.loadTasks().then(result => {
this.setState({tasks: result, unfilteredTasks: result})
})
}
onSearchChange(event) {
const value = event.target.value;
const propsToCheck = ['task.text', 'type', 'status', 'topic.name'];
this.setState(prevState => {
const tasks = prevState.unfilteredTasks.filter(obj => checkObjectContainsValue(obj, value)(propsToCheck));
return {tasks: tasks};
})
}
render() {
return <TasksPresentation
<TasksList
tasks={this.state.tasks}
onSearchChange={(event) => this.onSearchChange(event)}>
</TasksList>
</TasksPresentation>
}
With this I can see in the developer tools that the state of the component is being changed and that the tasks list is being filtered, but the lists is not being re-rendered. Why is that, how can I fix this?
Update
On inspecting further down the component chain. I can see that the tas list table component that is responsible for rendering of the list is a class component that uses prop rows which is the tasks list being send down from the parent component, to build state:
constructor(props) {
super(props);
this.state.data = this.transformRows(props.rows)
}

ReactJS : How to properly handle data fetched from food2fork API?

I have an app built with ReactJS. Its purpose is to display recipes, searched in food2fork API.
I have no problems with updating state of parent component. Data is fetched after clicking 'search' button in app.
My issue is related with sending fetched data as props to child component and properly displaying received recipes based on current search.
handleChange is only for handling input.
handleSearch is what I wanted to use 'onClick' of a button to display data fetched from API.
Fetched recipes should be displayed in Results component.
Hope it is clear :)
Besides only passing state as props from Parent component and using it in Child component, I also tried to update Child state based on received props with lifecycle methods - maybe I haven't used them corrently ...
Parent component:
import React, { Component } from 'react';
import Results from './Results';
class Recipes extends Component {
constructor(props){
super(props);
this.state = {
search: '',
recipes: []
}
}
handleChange=e=>{
this.setState({
search: e.target.value
})
}
handleSearch =()=>{
if(this.state.search !== ''){
const url = `https://www.food2fork.com/api/search?key=367d2d744696f9edff53ec5b33a1ce64&q=${this.state.search}`
fetch(url)
.then(data => data.json())
.then(jsonData => {
this.setState((jsonData)=> {return {
recipes: jsonData}
})
})
} else {
console.log('empty')
}
}
render() {
return (
<Wrapper>
<SearchBar
value={this.state.search}
type='search'
onChange={this.handleChange}>
</SearchBar>
<SearchButton onClick={this.handleSearch}>SEARCH</SearchButton>
<Results recipes={this.state.search}/>
</Wrapper>
);
}
}
export default Recipes;
CHILD COMPONENT 'Results' which should receive updated recipe list as props and display these recipes.
import React from 'react';
import Recipe from './Recipe';
class Results extends React.Component {
render(){
return (
<Container>
<RecipesList>
{this.props.recipes.map(item =>
<Recipe
f2fURL={item.f2f_url}
image={item.image_url}
publisher={item.publisher}
publisherURL={item.publisher_url}
recipeID={item.recipe_id}
source={item.source_url}
title={item.title}
/>)}
</RecipesList>
</Container>
);
}
};
As #yourfavoritedev mentioned, you have a typo on Results props. It should be
recipes={this.state.recipes} instead of recipes={this.state.search}
You should also change:
this.setState((jsonData)=> {return {
recipes: jsonData}
})
for:
this.setState({ recipes: jsonData })
The updater function will be something like this (documentation here):
(state, props) => stateChange
So the jsonData you are using on your setState is actually the previous state and not the data coming from the api call.
Your problem is here
this.setState((jsonData)=> {return {
recipes: jsonData}
})
inside your ajax response.
Change this to
this.setState({recipes: jsonData});
This should set the recipes object correctly.

Re-rendering react-redux component

I am using react-redux code structure and this is my first try with react-redux.I have cloned a github repository from Here and started editing it.
My directory structure:
Here schema is parent component and header and tables are 2 child components.Tables is showing data from localstorage through redux store.
Initializing store:
const initialState = JSON.parse(window.localStorage.getItem('test'));
const store = createStore(Reducers, initialState, compose(applyMiddleware(...middleware), extension));
Now an event is triggered from Header and sent to schema and in the response of this event schema is updating localstorage by requesting to server and saving server's response in localstorage as follows:
Schema.js:
class Schema extends PureComponent {
constructor(props) {
super(props);
this.table = '';
getTables();
}
myCallback = () => {//triggered by child(Header)
getTables();
}
getTables = () => {
axios.get(url)
.then((response) => {
if(response.data.status==0){
window.localStorage.setItem('test',JSON.stringify(response.data));
this.tables=JSON.parse(window.localStorage.getItem('test'))
});
}
render() {
console.log(this.tables);//this is giving updated value at each updation
return (
<div className='container-fluid'>
<Header callbackFromParent={ this.myCallback } />
<br />
<br />
<Tables val={ this.tables } />
</div>
);
}
}
Here is code for Tables.js:
class Tables extends Component {
props: Props
render() {
let {tables,val } = this.props;
console.log(JSON.parse(window.localStorage.getItem('test')));//this is giving updated value at each updation in localstorage
console.log(val);//this is also giving updated value at each updation in localstorage
tables=val;
console.log(tables);this is also updating in realtime.
return (
<div className='table-wrapper'>
{ tables.map((table) => (
<Table
key={ table.id }
data={ table }
/>
))}
</div>
);
}
}
type Props = {
tables: Array<TableType>
};
The issue is whenever header triggers callback, schema updates value of localstorage, this updation also re render Tables component. Also an updated value can be seen in render of Tables component but the tables which are shown are from previous saved value. To get current value in tables, we need to refresh the page.
is it a mistake in code flow or i need something else for this?
The idea is that react will trigger rendering of component whenever the component state or props is updated.
If the component props are updated in parent component you will still need to update the component state to make the next render in inner component
The key of this is using componentWillReceiveProps
I updated your code with the code below:
Basically i did the following:
1- I used component state for Schema, Tables, and Table
2- I used this.setState whenever i need to make updates to state (this is very important)
3- I make sure that when a component props is updated in parent i update the component state as well using componentWillReceiveProps and this will make the new render with updated data
Schema component:
class Schema extends Component {
constructor(props) {
super(props);
this.state = { tables : { } }
this.getTables = this.getTables.bind(this);
this.myCallback = this.myCallback.bind(this);
}
componentWillMount(){
this.getTables();
}
myCallback = () => {
//triggered by child(Header)
this.getTables();
}
getTables = () => {
axios.get(url)
.then((response) => {
if(response.data.status==0)
{
window.localStorage.setItem('test',JSON.stringify(response.data));
this.setState({
tables : JSON.parse(window.localStorage.getItem('test'))
});
}
);
}
render() {
//this is giving updated value at each updation
console.log(this.state.tables);
return (
<div className='container-fluid'>
<Header callbackFromParent={ this.myCallback } />
<br />
<br />
<Tables tables={this.state.tables} />
</div>
);
}
}
Tables Component
class Tables extends Component {
constructor(props) {
super(props);
this.state = { tables : { } }
}
componentWillMount(){
this.setState({
tables : this.props.tables
})
}
componentWillReceiveProps(nextProps){
this.setState({
tables : nextProps.tables
})
}
render() {
console.log(JSON.parse(window.localStorage.getItem('test')));//this is giving updated value at each updation in localstorage
console.log(this.state.tables);//this is also giving updated value at each updation in localstorage
return (
<div className='table-wrapper'>
{ this.state.tables.map((table) => (
<Table key={ table.id } data={ table } />
))
}
</div>
);
}
}
And finally a dummy Table component to show that you will also need to handle props update using componentWillReceiveProps to make sure each individual table component did render after props update
And probably this is where you have the issue ... because the tables are showing old data but the console.log of Tables component is logging new data which means Each individual Table component is not rending after the update
class Table extends Component {
constructor(props) {
super(props);
this.state = { data : { } }
}
componentWillMount(){
this.setState({
data : this.props.data
})
}
componentWillReceiveProps(nextProps){
this.setState({
data : nextProps.data
})
}
render() {
console.log(this.state.data);
return (
<table className='table'>
{this.state.data}
//use your table data here
</table>
);
}
}
Important Edit:
As mentioned by react documentation componentWillReceiveProps might get called even if the props have not changed, thats why in some situation you might need to consider comparing this.props with nextProps to make sure that you really got new updated props and based on that you update the component state ....

Resources