class CyInfo extends Component {
foo(){
console.log(this.props.id);
return getAttributes(this.props.id)
}
render() {
return ( <Info data = {this.foo()}> </Info>)
}
}
this parent receive "props.id" and pass data value to children which is returned by getAttributes().
export default class Info extends Component {
constructor(props){
super(props)
}
/*componentWillReceiveProps(nextProps){
console.log(nextProps);
*/
render() {
console.log(this.props.data);
return (
<div id="info">{this.props.data}</div>
)
}
}
On child i can see props value on the console and in componentWillReceiveProps also.But array not rendering.
I try the use react-devtool. In react-devtool props seems passes the children but not rendering. Interestingly in react-devtool when i change the some of array's element array is rendering.
What did i do wrong.
EDIT:
[React-Devtool Screenshot][1]
I edited the react-devtool screenshot. Props are seems but component only renders initial value. In screenshot console error is favicon just ignore this
EDIT2:Console prints props array
EDIT 3:
JSON.stringify(this.props.data)
The props are coming from function(getattributes) which is call a method asynchronous and when this props passed the child there are not rendering.
I call async method directly in parent child and set state with callback in componentWillReceiveProps:
componentWillReceiveProps(nextProps){
self = this;
AsyncFunc(nextProps.id ,(error, result) => {
self.setState({data:result})
});
}
and render with
return (<div id="info">
{Array.isArray(this.state.data) && this.state.data.map((data) => {
return <div key={data._id}>{data.class}{data.predicate}{data.yuklem}</div>
})}</div>
)
As foo is a function, you have to pass to child component as:
return ( <Info data = {() => this.foo()}> </Info>)
Also, data is an array, you have to render using .map(), as follows:
export default class Info extends Component {
constructor(props){
super(props)
}
/*componentWillReceiveProps(nextProps){
console.log(nextProps);
*/
render() {
console.log(this.props.data);
return (
<div id="info">{this.props.data.map(( element, index ) => {
console.log(element);
<span key={index}> {element}</span>})}
</div>
)
}
}
As you have mentioned that this.data.props returns an array, and in order to render elements within an array, you need to map over the array elements and also check that the data is an array or not before rendering as initially the value may not be available or not an array
export default class Info extends Component {
constructor(props){
super(props)
}
/*componentWillReceiveProps(nextProps){
console.log(nextProps);
*/
render() {
console.log(this.props.data);
return (
<div id="info">
{this.props.data && Array.isArray(this.props.data) && this.props.data.map((data, index) => {
return <div key={index}>{data}</div>
})}</div>
)
}
}
Related
I have two components, one parent one child. I am using the fetch method in componentDidMount() callback. Once I do this, I set the state with key items to that data that is pulled from the api. Once I do this it should be able to be console logged in the child component as a prop. However this is not working. What am I doing wrong here?
Parent Component:
import React, {Component} from 'react';
import Map from './maps/Map';
class Main extends Component {
constructor(props){
super(props);
this.state = {
name: "John",
items: []
}
}
componentDidMount() {
fetch('https://hn.algolia.com/api/v1/search?query=')
.then(dat => dat.json())
.then(dat => {
this.setState({
items: dat.hits
})
})
}
render() {
return (
<div>
<Map list={this.state.name} items={this.state.items}></Map>
</div>
)
}
}
export default Main;
Child Component:
import React, {Component} from 'react';
class Map extends Component {
constructor(props) {
super(props);
console.log(props.items)
}
render () {
return (
<h1>{this.props.name}</h1>
)
}
}
export default Map;
First, fetch is asynchronous. So, the fetch statement might be pending by the time you try to console.log the result inside the child constructor.
Putting the console.log inside the render method would work, because the component will be rerendered, if the state items changes.
The constructor for a component only runs one time during a lifecycle. When it does, props.items is undefined because your ajax request is in-flight, so console.log(props.items) doesn't show anything.
If you change your constructor to console.log("constructed");, you'll see one-time output (stack snippets may not show this--look in your browser console). Henceforth, componentDidUpdate() can be used to see the new props that were set when your ajax request finishes.
You could also log the props inside the render method, which will run once before the ajax request resolves and again afterwards when props.items changes.
As a side point, you have <Map list=... but the component tries to render this.props.name, which is undefined.
Also, if you aren't doing anything in the constructor (initializing state or binding functions) as here, you don't need it.
class Map_ /* _ added to avoid name clash */ extends React.Component {
constructor(props) {
super(props);
console.log("constructed");
}
componentDidUpdate(prevProps, prevState) {
const props = JSON.stringify(this.props, null, 2);
console.log("I got new props", props);
}
render() {
return (
<div>
<h1>{this.props.name}</h1>
<pre>
<ul>
{this.props.items.map((e, i) =>
<li key={i}>{JSON.stringify(e, null, 2)}</li>)}
</ul>
</pre>
</div>
);
}
}
class Main extends React.Component {
constructor(props) {
super(props);
this.state = {name: "John", items: []};
}
componentDidMount() {
fetch('https://hn.algolia.com/api/v1/search?query=')
.then(dat => dat.json())
.then(dat => {
this.setState({items: dat.hits})
});
}
render() {
return (
<div>
<Map_
name={this.state.name}
items={this.state.items}
/>
</div>
);
}
}
ReactDOM.createRoot(document.querySelector("#app"))
.render(<Main />);
<script crossorigin src="https://unpkg.com/react#18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.development.js"></script>
<div id="app"></div>
The only problem you have is that you are trying to use this.props.name and your Map component props are called list and items, so it will return undefined.
If you log your props in the constructor you will get the initial state of Main because the fetch hasn't returned anything yet. Remember that the constructor only runs once. So you are probably getting an empty array when you log props.items in the constructor because that's what you have in your initial state.
{
name: "John",
items: []
}
If you log the props in your render method you will see your array filled with the data you fetched, as you can see here:
https://codesandbox.io/s/stoic-cache-m7d43
If you don't want to show the component until the data is fetched you can include a boolean property in your state that you set to true once you the fetch returns a response and pass it as a prop to your component. Your component can you use that variable to show, for example, a spinner while you are fetching the data. Here's an example:
https://codesandbox.io/s/reverent-edison-in9w4
import CircularProgress from "#material-ui/core/CircularProgress"
class Main extends Component {
constructor(props) {
super(props);
this.state = {
name: "John",
items: [],
fecthed: false
};
}
componentDidMount() {
fetch("https://hn.algolia.com/api/v1/search?query=")
.then(dat => dat.json())
.then(dat => {
this.setState({
items: dat.hits,
fecthed: true
});
});
}
render() {
return (
<Map
fetched={this.state.fecthed}
list={this.state.name}
items={this.state.items}
/>
);
}
}
class Map extends Component {
render() {
return (
<div>
{this.props.fetched ? (
<div>
<h1>{this.props.list}</h1>
{this.props.items.map((item, indx) => (
<div key={indx}>Author: {item.author}</div>
))}
</div>
) : (
<CircularProgress />
)}
</div>
);
}
}
Hope this helps. Cheers!
I am calling a handle method (to change state) in a <grandchild> component but it stop rendering after a couple of callback in the <grandparent> component.
I have tried to:
setting bind correctly with both this.bind in construct and arrow method.
making sure the call back is call everytime the prop.callback is call.
This is an example of what I'm trying to do with graphql server:
Grandparent Component
//Graphql constant for query
const ApolloConstant = gpl`
...etc
class Grandparent extends Component {
constructor(props) {
super(props);
this.state = { vars: 'query_string' }
}
handler = (args) => {
this.setState({vars: args})
}
render() {
return (
// For requerying graphql with search
<input onChange={() => this.setState(vars: e.target.value)} />
<Query variable={this.state.vars}>
...code -> some_data_arr
{<ChildComponent data={some_data_arr} handler={this.handler}/>}
</Query>
);
}
}
Child Component
//This component will take in an arr of obj and display a obj list
// if one of the obj is clicked then render a child component to display that single obj
class Child extends Component {
constructor(props) {
super(props);
this.state = {
singleData: null
}
}
render() {
return (
// Ternary operator here for conditional rendering
{
this.state.singleData
? <Grandchild data={this.state.singleData} handleParentData={this.props.handler} />
: this.display_data(this.props.data)
}
);
}
//Method to call to display objects
display_data = () => {
this.props.map() =>
<div onClick={this.setState({singleData: data})} > ...some code to display data <div/>
}
}
Grandchild Component
class Grandchild extends Component {
render() {
return (
{...do some code with object props here}
<Button onclick={(this.props.handleParentData(vars))} >Btn</Button>
);
}
}
When I test this, everything works for the first 3-4 render then no more re-rendering even though the callback is going through. I check to see if the method in <grandparent> is being call and it does but the state stop changing. Even after going to a new route (react router) and then coming back, I still cant change state with that callback.
<Button onclick={(this.props.handleParentData(vars))} >Btn</Button>
I think the problem is the function being called right into the onclick prop, you should probably have it wrapped in another function so it is only called when you actually trigger the onclick listener:
handleClick = () => {
this.props.handleParentData(vars)
}
render() {
return (
{...do some code with object props here}
<Button onclick={(this.handleClick)} >Btn</Button>
);
}
The following is the state set:
State to be passed
which I pass in the SearchResults Component:
SearchResults isRemoval='false' onAdd={this.addTrack}
searchResults={this.state.searchResults}/>
(im aware that the beginning is missing the < symbol I had to leave it out because it was hiding it with the < not omitted)
and then in SearchResults I pass the props through to
class SearchResults extends React.Component {
render() {
return (
<div className="SearchResults">
<h2>Results</h2>
<TrackList isRemoval={this.props.isRemoval} onAdd={this.props.onAdd}
tracks={this.props.searchResults}/>
</div>
)
}
}
And then in Tracklist I try to use map on the array but I get an error saying it cant perform map on undefined.
class TrackList extends React.Component {
render() {
return (<div className="TrackList">
{
this.props.tracks.map(track => {
return <Track track={track} />
})
}
</div>
);
}
}
When I call the props in the first component down it is defined and I can use the array as desired but when passed to the second component it shows up undefined in console and I can not use map. Does any one know why this is happening?
So the problem was I was passing props to the same component from another component that had an error in it. I had forgotten that I was passing props from more than one source thats why I was getting the undefined error. Thanks everyone for your help.
Are you loading that data through something external, and if so does your state have a default value? If you're not setting searchResults as an empty array, initially you won't have a value for searchResults.
If that's not the case, there must be some part of the program we're missing here, because the code seems fine.
Try logging each component's entire props every step of the way, you'll get a better idea of when it gets lost.
This is an interesting issue you're facing, but I would suggest that you first check if this.props.tracks is null || undefined before trying to map through it.
If my hypothesis is correct, when the render method for the TrackList component initially runs, this.props.tracks is undefined, therefore it probably throws an error.
The following code could possibly solve your issue:
class TrackList extends React.Component {
render() {
return(
const { tracks } = this.props;
<div className="TrackList">
{tracks ? tracks.map(track => {
return <Track track={track} />;
}) : null}
</div>
);
}
}
Child component updates when its state or the parent's state changes but not if parent's props changes.
Create state in SearchResults and pass that state to child component
class SearchResults extends React.Component {
constructor(){
super();
this.state = {
tracks: []
}
}
componentWillReceiveProps(newProps){
if(newProps.searchResults){
this.setState({
tracks: newProps.searchResults
});
}
}
render() {
return (
<div className="SearchResults">
<h2>Results</h2>
<TrackList isRemoval={this.props.isRemoval} onAdd={this.props.onAdd}
tracks={this.state.tracks}/>
</div>
)
}
}
Same Applies to other props too..
Hope this helps
How can I pass props to a component of a child page?
The prop that I am trying to pass is onToggleBooking: PropTypes.func which is defined in my layout.js (root file) as
lass Template extends React.Component {
constructor(props) {
super(props)
this.state = {
isBookingVisible: false,
}
this.handleToggleBooking = this.handleToggleBooking.bind(this)
}
handleToggleBooking() {
this.setState({
isBookingVisible: !this.state.isBookingVisible
})
}
render() {
const { children } = this.props
return (
<main className={`${this.state.isBookingVisible ? 'is-booking-visible' : ''}`}>
{children()}
</main>
)
}
}
Template.propTypes = {
children: PropTypes.func
}
export default Template
I want to pass onToggleBooking={this.handleToggleBooking} prop to {children()} so I am able to pass and use in a component of one of the child pages.
To do this I tried
{
children.map(child => React.cloneElement(child, {
onToggleBooking
}))
}
But I receive an error of children.map is not defined.
First, it's ideal to render children via props like:
render() {
return <div>{ this.props.children }</div>
}
Rendering children as prop functions is doable but you hould take a look to ensure your class the children are extended from is configured correctly, and the resulting render template is valid:
class CoolClass extends Component {
render() {
return this.props.children()
}
}
And then the template you call when rendering should look like:
<CoolClass>
{() => <h1>Hello World!</h1>}
</CoolClass>
You are close with passing sown the toggle handler using onToggleBooking={this.handleToggleBooking}, but it needs to be provided as a prop itself on a component or child your passing down. You can either edit the constructor to include it with your props.children, but that may be a pain to debug correctly calling children() prop as a fucntion.
I have a component ParentToDataDisplayingComponent that is creating a few lookups to help format data for a child component based on data in a redux store accessed by the parent of ParentToDataDisplayingComponent.
I am getting some lagging on the components rerendering, where the changing state has not affected this.props.dataOne or this.props.dataTwo - the data in these lookups is guaranteed the same as last render, but the data in props is not guaranteed to be the available (loaded from the backend) when the component mounts. mapPropsToDisplayFormat() is only called after all of the data passed in through the props is available.
I would like to declare the lookup variables once, and avoid re-keyBy()ing on every re-render.
Is there a way to do this inside the ParentToDataDisplayingComponent component?
export default class ParentToDataDisplayingComponent extends Component {
...
mapPropsToDisplayFormat() {
const lookupOne = _(this.props.dataOne).keyBy('someAttr').value();
const lookupTwo = _(this.props.dataTwo).keyBy('someAttr').value();
toReturn = this.props.dataThree.map(data =>
... // use those lookups to build returnObject
);
return toReturn;
}
hasAllDataLoaded() {
const allThere = ... // checks if all data in props is available
return allThere //true or false
}
render() {
return (
<div>
<DataDisplayingComponent
data={this.hasAllDataLoaded() ? this.mapPropsToDisplayFormat() : "data loading"}
/>
</div>
);
}
}
Save the result of all data loading to the component's state.
export default class ParentToDataDisplayingComponent extends Component {
constructor(props) {
super(props)
this.state = { data: "data loading" }
}
componentWillReceiveProps(nextProps) {
// you can check if incoming props contains the data you need.
if (!this.state.data.length && nextProps.dataLoaded) {
this.setState({ data: mapPropsToDisplayFormat() })
}
}
...
render() {
return (
<div>
<DataDisplayingComponent
data={this.state.data}
/>
</div>
);
}
}
I think depending on what exactly you're checking for in props to see if your data has finished loading, you may be able to use shouldComponentUpdate to achieve a similar result without saving local state.
export default class ParentToDataDisplayingComponent extends Component {
shouldComponentUpdate(nextProps) {
return nextProps.hasData !== this.props.hasData
}
mapPropsToDisplayFormat() {
...
toReturn = data.props.dataThree
? "data loading"
: this.props.dataThree.map(data => ... )
return toReturn;
}
render() {
return (
<div>
<DataDisplayingComponent
data={this.mapPropsToDisplayFormat()}
/>
</div>
);
}
}