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.
Related
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.
I have the following code that simply constructs blocks for our products and the selected state allows the component to be selected and unselected. How can I figure out which of these components are selected and limit the user to only selecting one at a time. This is ReactJS code
import React from 'react';
export default class singleTile extends React.Component{
constructor(props){
super(props);
this.title = this.props.title;
this.desc = this.props.desc;
this.svg = this.props.svg;
this.id = this.props.id;
this.state = {
selected: false
}
}
selectIndustry = (event) => {
console.log(event.currentTarget.id);
if(this.state.selected === false){
this.setState({
selected:true
})
}
else{
this.setState({
selected:false
})
}
}
render(){
return(
<div id={this.id} onClick={this.selectIndustry}className={this.state.selected ? 'activated': ''}>
<div className="icon-container" >
<div>
{/*?xml version="1.0" encoding="UTF-8"?*/}
{ this.props.svg }
</div>
</div>
<div className="text-container">
<h2>{this.title}</h2>
<span>{this.desc}</span>
</div>
</div>
)
}
}
You need to manage the state of the SingleTile components in the parent component. What i would do is pass two props to the SingleTile components. A onClick prop which accepts a function and a isSelected prop that accepts a boolean. Your parent component would look something like this.
IndustrySelector.js
import React from 'react';
const tileData = [{ id: 1, title: 'foo' }, { id: 2, title: 'bar' }];
class IndustrySelector extends Component {
constructor(props) {
super(props);
this.state = { selectedIndustry: null };
}
selectIndustry(id) {
this.setState({ selectedIndustry: id });
}
isIndustrySelected(id) {
return id === this.state.selectedIndustry;
}
render() {
return (
<div>
{tileData.map((data, key) => (
<SingleTile
key={key}
{...data}
onClick={() => this.selectIndustry(data.id)}
isSelected={this.isIndustrySelected(data.id)}
/>
))}
</div>
);
}
}
The way this works is as follows.
1. Triggering the onClick handler
When a user clicks on an element in SingleTile which triggers the function from the onClick prop, this.selectIndustry in the parent component will be called with the id from the SingleTile component.
Please note that in this example, the id is remembered through a
closure. You could also pass the id as an argument to the function of
the onClick prop.
2. Setting the state in the parent component
When this.selectIndustry is called it changes the selectedIndustry key of the parent component state.
3. Updating the isSelected values form the SIngleTile components
React will automatically re-render the SingleTile components when the state of the parent component changes. By calling this.isIndustrySelected with the id of the SingleTile component, we compare the id with the id that we have stored in the state. This will thus only be equal for the SingleTile that has been clicked for the last time.
Can you post your parent component code?
It's not so important, but you can save some time by using this ES6 feature:
constructor(props){
super(props);
const {title, desc, svg, id, state} = this.props;
this.state = {
selected: false
}
}
I have a React component that contains an array of child components. The parent retrieves data from a service and stores it in state. It passes an item from the data array to each child component via props.
The child component includes functionality that updates a value in its data item. When it does this, it fires an event, passing the updated item back to the parent. The parent creates a new state array, including the updated item.
Simplified code below.
This all works fine, and the update array is processed in the parent's render method. However, the child components are never re-rendered, so the updated property remains at its previous value.
How can I get the relevant child component to display the updated status?
class SearchView extends Component {
pageSize = 20;
constructor(props) {
super(props);
this.state = {
searchTerm: this.props.searchTerm,
results: []
};
}
getResults = (page) => {
const from = (page - 1) * this.pageSize;
searchActions.termSearch(this.state.searchTerm, from, this.pageSize).then(response => {
const results = response.SearchResultViews;
this.setState({
results: results
});
});
}
componentDidMount() {
this.getResults(1);
}
refresh(result){
const results = this.state.results.map(r => {
return (r.Id === result.Id) ? result : r;
});
this.setState({
results: results
});
}
render() {
let items = [];
if (this.state.results.length > 0) {
items = this.state.results.map((result, i) => {
return <SearchItem key={i} result={result} onStatusUpdate={(r) => this.refresh(r)}></SearchItem>;
});
}
return (
<div className="r-search-result">
<Row className='clearfix scroller'>
<div className='r-container-row results'>
{ items }
</div>
</Row>
</div>
);
}
}
class SearchItem extends Component {
constructor(props) {
super(props);
}
updateStatus(newValue) {
resourceActions.updateStatus(newValue);
//Bubble an event to the Parent to refresh the result and view
if (props.onStatusUpdate) {
searchActions.get(props.result.Id).then((result) => {
props.onStatusUpdate(result);
});
}
}
render() {
return (
<a href={this.props.result.link}>
<span className="column icon-column">{this.props.result.imageUrl}</span>
<span className="column title-column">{this.props.result.titleLink}</span>
<span className="column status-column">{this.props.result.status}</span>
<span className="column button-column"><button onClick={() => this.UpdateStatus(5)}></button></span>
</a>
);
}
}
Edit
In my actual (non-simplified) app, the child component transforms the props it has been passed in the ComponentDidMount() method, and it sets values in state; the render method binds the markup against state, not props. After putting a breakpoint in the child's Render() method as suggested by #Vishal in the comments, I can see that the updated data is received by the child, but since the state hasn't been updated, the component doesn't display the updated data.
The question then is, how best to update the component state without causing an infinite render loop?
In the end, I solved the problem by transforming the properties into state for the child component's in componentWillUpdate(), as well as the componentDidMount() method. As illustrated in the code below:
componentDidMount() {
if (this.props.result) {
this.prepareRender();
}
}
componentWillUpdate(nextProps, nextState) {
if (nextProps.result.status!== this.props.result.status) {
this.prepareRender();
}
}
prepareRender() {
//simplified
this.setState({
imageUrl: this.props.result.imageUrl,
titleLink: this.props.result.titleLink,
status: this.props.result.status
});
}
render() {
return (
<a href={this.props.result.link}>
<span className="column icon-column">{this.state.imageUrl}</span>
<span className="column title-column">{this.state.titleLink}</span>
<span className="column status-column">{this.state.status}</span>
<span className="column button-column"><button onClick={() => this.UpdateStatus(5)}></button></span>
</a>
);
}
UPDATE
In React 16.3 the componentWillUpdate() method is deprecated. This solution should use the new getDerivedStateFromProps() lifecycle method, as explained here: https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html.
My React component renders a twitter stream. The original version works correctly, but the componentDidMount method mutates the component's state:
(Original) components/Stream.js
import React, { Component } from 'react';
import { Button } from 'react-bootstrap';
class Stream extends Component {
constructor() {
super();
this.state = { streamItems: [] }
}
componentDidMount() {
fetch('/tweets')
.then(res => res.json())
.then(tweets => this.setState({ streamItems: tweets }));
}
render() {
return (
<div>
<h1>Tweets</h1>
<div className='stream-items'>
{this.state.streamItems.map(tweet =>
<div key={tweet.id}>{tweet.text}</div>
)}
</div>
<Button
className='btn-remove'
onClick={() => this.props.removeStream(this.props.stream.id)}
>
Remove Stream
</ Button>
</div>
)
}
}
export default Stream;
To prevent the component's state being mutated I setState using concat instead:
components/Stream.js (updated method only)
componentDidMount() {
fetch('/tweets')
.then(res => res.json())
.then(tweets => this.setState({
streamItems: this.state.streamItems.concat([tweets])
}))
}
I now receive the error:
Warning: Each child in an array or iterator should have a unique
"key" prop.
This is confusing as I have set the key in the component's render method.
Warning: Each child in an array or iterator should have a unique "key" prop.
streamItems: this.state.streamItems.concat([tweets])
Its look like tweets is not having id.Its coming undefined.So,React throwing an error. Or you need to spread it
streamItems: this.state.streamItems.concat([...tweets])
Note:
You don't need to use concat.You can do like this.
streamItems:[...this.state.streamItems,tweets]
What refreshes the view in react or is the code always live displayed?
I have a function called removeAdmin and makeAdmin which adds and removes users as Admins and then when a user is an admin the render of Member component renders and admin shield logo. It works fine but I'm wondering whether render is being triggered each time I change the UI using a function or if render is live listening to changes in it's components?
class MemberList extends Component {
constructor(props) {
super(props)
this.state = {
members: [],
loading: false,
administrators: []
}
this.makeAdmin = this.makeAdmin.bind(this)
this.removeAdmin = this.removeAdmin.bind(this)
}
componentDidMount(){
this.setState({loading:true})
fetch('https://api.randomuser.me/?nat=US&results=12')
.then(response => response.json())
.then(json => json.results)
.then(members => this.setState({
members,
loading:false
}))
}
makeAdmin(email){
const administrators = [
...this.state.administrators,
email
]
this.setState({administrators})
}
removeAdmin(email){
const administrators = this.state.administrators.filter(
adminEmail => adminEmail !== email
)
this.setState({administrators})
}
render() {
const { members, loading } = this.state
return (
<div className="member-list">
<h1>Society Members</h1>
{
(loading) ?
<span> loading...</span>:
<span>{members.length} members</span>
}
{ (members.length)?
members.map(
(member, i) =>
<Member key={i}
// This admin prop is worked out by enumerating through the administrator
// array with some(). some() passes in the enumerators, checking whether
// the current member in members.map() exists in the administrators array
// and returns admin=true if so.
admin={this.state.administrators.some(
adminEmail => adminEmail === member.email
)}
name={member.name.first + '' + member.name.last}
email={member.email}
thumbnail={member.picture.thumbnail}
makeAdmin={this.makeAdmin}
removeAdmin={this.removeAdmin}/>
):
<span>Currently 0 members</span>
}
</div>
)
and the Member component:
class Member extends Component {
componentWillMount(){
this.style={
backgroundColor: 'grey'
}
}
render() {
const { name, thumbnail, email, admin, makeAdmin, removeAdmin } = this.props
return (
<div className="member" style={this.style}>
<h1>{ name } {(admin) ? <FaShield/> : null}</h1>
<div>
<img src={ thumbnail }/>
</div>
<div>
{
(admin)?
<Button title="Make Admin" onClick={() => removeAdmin(email) } color="#841584"> Remove Admin </Button>
:
<Button title="Make Admin" onClick={ () => makeAdmin(email) } color="#841584"> Make Admin </Button>
}
<a href={`mailto:${ email }`}><p> {email} </p></a>
</div>
</div>
)
}
}
export default Member
What triggers a new render on components is when the state changes or when receiving new properties.
There are two main objects that drive the render in each component, the this.props and the this.state. If any of this objects gets updated then the render method gets executed.
The this.props object gets updated whenever you send new properties to the childrens. this.state gets updated using the this.setState method.
That being said, it's really important to keep track of the properties you send to the children, as a rule of thumb I always recommend not using the spread operator to pass props to the children, for example:
<Parent>
<Child {...this.props} />
</Parent>
I'd avoid that pattern because if any of the props changes, than all props are sent to the child. Instead I recommend sending only what the children needs.
<Parent>
<Child some={this.props.value} />
</Parent>
You need to be very careful when you need to render your component, otherwise it's so easy to re-render everything! Which will lead to performance issues.
It depends on what you define your component render method to be.
It can change based on the state or props that you give it.
Less frequent, but you can check out shouldComponentUpdate as it allows you to overwrite the method to give it more “smarts” if you need the performance boost.