I'm trying to transfer data from parent RN Component to child.
My parent RN Component:
export default class ListOfUserPhotos extends Component {
constructor(props) {
super(props);
this.state = {
...
};
}
componentDidMount() {
...
}
render() {
return this.state.photosKeysArray.map((photo, index) => {
console.warn('photoData - ' + photo)
// returns 'photoData - {photoValue}' - everything is OK here
return <ListOfUserPhotos_ListView key = {index}
description = {photo.description}
...
/>
})
}
}
};
My child RN Component:
export default class ListOfUserPhotos_ListView extends Component {
render() {
return(
<View style = {listOfUserPhotosStyle.container_list}>
{console.warn('desc = ' + this.props.description)}
// returns 'desc = undefined' - everything is BAD here
...
</View>
)
}
}
Before passing data to the child component I can console that data and see it there. But inside the component the props are undefined.
Can someone explain me what I did wrong? Or transferring data between RN Component should be implemented in other way?
As you are retrieving the data asynchronously you should do the following to ensure that the component gets re-rendered.
componentDidMount() {
// You need to set the state here to cause a re-render
this.setState({
photosKeysArray: firebase.response
});
}
render() {
const { photosKeysArray } = this.state;
if(photosKeysArray.length === 0) {
return <Text>Loading...</Text>
}
// this will be returned if above condition is not met
return photosKeysArray.map((photo, index) => {
return (
<ListOfUserPhotos_ListView
key = {index}
description = {photo.description}
/>
);
})
};
Related
I am running a client and server setup (react and axios api calls) And I would like to understand how to access the returned data from my child components within the React Framework. I have the connection working to the http server, however i lack the foundational knowledge of working with this.state or props.
here is effectively my app.js
import React, { Component } from 'react';
import axios from 'axios';
import ChildComponent from "./components/childComponent"
class App extends Component {
state = {
data: [],
intervalIsSet : false
};
componentDidMount() {
this.getDataFromDb();
if (!this.state.intervalIsSet) {
let interval = setInterval(this.getDataFromDb, 10000);
this.setState({ intervalIsSet: interval });
}
}
getDataFromDb = () => {
fetch("http://localhost:3001/api/getData")
.then(data => data.json())
.then(res => this.setState({ data: res.data }));
};
render() {
const { data } = this.state;
return (
<div>
<childComponent />
</div>
);
};
}
export default App;
and here is the child component. --> my intention is to simply access (or print out) my returned data from the server from within the child component.
import React, { Component } from 'react';
class ChildComponent extends React.Component {
render() {
return (
{this.props.data}
);
}
}
export default ChildComponent;
First make sure you uppercase the first letter of ChildComponent. If you want to pass data you should add that object as an attribute to the component, and then access it throught this.props. Also you need to render a single top element, and if you don't need div or any other html element, you can wrap it with React.Fragment.
Regarding to data, if its an array you can simply map through it and return data you want to see, if you want the entire object to show as a string, you can use JSON.stringify(). And if that's an object you can show only data you want.
class App extends React.Component {
//...
render() {
const { data } = this.state;
return (
<div>
<ChildComponent data={data} />
</div>
);
}
}
//for array, ex: data = ["first","name"];
class ChildComponent extends React.Component {
render() {
return (
<React.Fragment>
{this.props.data.map(item =>
<p>{item}</p>
)}
</React.Fragment>
);
}
}
//for object, ex: data = {id: 1, name: 'John'};
class ChildComponent extends React.Component {
render() {
const {data} = this.props;
return (
<React.Fragment>
<p>{data.id}</p>
<p>{data.name}</p>
</React.Fragment>
);
}
}
//for single value (string, int, etc), ex: data = "my name";
class ChildComponent extends React.Component {
render() {
return (
<React.Fragment>
<p>{this.props.data}</p>
</React.Fragment>
);
}
}
//to show object as a string (could be any object mentioned before)
class ChildComponent extends React.Component {
render() {
return (
<React.Fragment>
{JSON.stringify(this.props.data)}
</React.Fragment>
);
}
}
You can pass down the data array as the data prop to the child component. Make sure you also uppercase the first character in the component name, or it won't work properly.
A component needs to render a single top level element, so you could e.g. render a div with the JSON stringified data prop inside of it in the child component.
class App extends React.Component {
// ...
render() {
const { data } = this.state;
return (
<div>
<ChildComponent data={data} />
</div>
);
}
}
class ChildComponent extends React.Component {
render() {
return <div>{JSON.stringify(this.props.data)}</div>;
}
}
I am currently in a project, and I have had to do null checks on every single props that has come in to children components wether through redux or passing it in myself. I feel like that is not normal with react? Isn't a huge plus side of React is automatic re-rendering? If I try to put anything into state, I can't because There has to be a null check in the render before I do anything with the data. Thanks in advance!!
PARENT COMPONENT =
class App extends Component {
componentDidMount(){
//where I load the data
this.loadCardsFromServer();
this.props.GetAllData();
}
render() {
//NEED TO DO A NULL CHECK FROM THIS COMING FROM REDUX
const filteredData = !!this.state.data ? this.state.data.filter(card =>{
return card.name.toUpperCase().includes(this.state.input.toUpperCase())
}) : null;
return (
//MAKES ME DO ANOTHER NULL CHECK
<div>
{!!this.state.data ? filteredData.map(i => <Card person={i} key={i.created} planet={this.props.planets} />) : null}
</div>
))}
CHILD COMPONENT OF CARD
class Card extends Component {
//WHERE I WANT TO PUT THE PROPS
constructor(){
super();
this.state={
edit: false,
name: this.props.person.name,
birthYear: this.props.person.birth_year
}
}
render() {
let world = null;
//ANOTHER NULL CHECK
if(this.props.planet){
this.props.planet.map(i => {
if(i.id === this.props.person.id){
world = i.name
}
})
}
return (
<div>
//THIS IS WHERE I WANT THE VALUE TO BE STATE
{this.state.edit ? <input label="Name" value={this.state.name}/> : <div className='card-name'>{name}</div>}
</div>
You need to update state when data arrive.
You can do it like this:
import React, { Component } from 'react';
import './App.scss';
import Card from './Components/Card/Card.js';
class App extends Component {
constructor(){
super();
this.state = {
loading:true,
cards:[]
};
}
componentDidMount(){
this.loadCardsFromServer();
}
loadCardsFromServer = () => {
let cardsResponseArray = [];
// fetch your cards here, and when you get data:
// cardsResponseArray = filterFunction(response); // make function to filter
cardsResponseArray = [{id:1,name:'aaa'},{id:2,name:'bbb'}];
setTimeout(function () {
this.setState({
loading:false,
cards: cardsResponseArray
});
}.bind(this), 2000)
};
render() {
if(this.state.loading === true){
return(
<h1>loading !!!!!!!!</h1>
);
} else {
return (
<div>
{this.state.cards.map(card => (
<Card key={card.id} card={card}></Card>
))}
</div>
);
}
}
}
export default App;
And then in your Card component:
import React, { Component } from 'react';
class Card extends Component {
constructor(props){
super(props);
this.props = props;
this.state = {
id:this.props.card.id,
name:this.props.card.name
};
}
render() {
return (
<div className={'class'} >
Card Id = {this.state.id}, Card name = {this.state.name}
</div>
);
}
}
export default Card;
For those interested about React state and lifecycle methods go here
Okay, in this case i craft a little helper's for waiting state in my redux store. I fetch data somewhere (app) and i render a connected component waiting for fetched data in store:
const Loader = (props) => {
if (!props.loaded) {
return null;
}
<Card data={props.data}/>
}
const CardLoader = connect (state => {
return {
loaded: state.data !== undefined
data: state.data
}
})(Loader)
<CardLoader />
I took over a React Native project from somebody else, this project is quite old (2 years) and I came across the following:
Home.js Component: (I simplified it)
export let customersData = null;
export default class Home extends Component {
render() {
return (
<JumboButton
onPress={() => {
this.props.navigator.push({
component: CustomerSearch
});
}}
>
);
}
_getAllCustomers(limit, sortAttr, order) {
apiCall.(apiUrl, {
...
}).then((responseData) => {
const customersDataAll = responseData.data;
customersData = customersDataAll.filter((f) => {
return f.lastname !== ''
});
});
}
}
So within the Home-Component, customersData is filled with data. Also the component CustomerSearch is called and within CustomerSearch I found this:
CustomerSearch.js:
import {customersData} from './Home';
export default class CustomerSearch extends Component {
constructor(props) {
super(props);
this.ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
this.state = {
dataSource: this.ds.cloneWithRows(customersData),
};
}
}
Two things are a bit weird for me:
Is it correct to set customersData and not this.customersData inside the callback of the api call?
Currently I get this error https://d.pr/i/IUzxdf "Cannot convert undefined or null to object" and I assume that this is because of the data import of customersData in CustomerSearch.js. Is the place where I need to look? Btw is there any chance that react tells me the exact line and file where this error occurs?
Thanks for your help!
Short answer is that it is definitely an unusual React pattern, and not one that many people would recommend. Importing a let variable into another file is not a reliable way to share information between components.
It would be far more sensible to attach customersData to your parent component's state and pass it to CustomersSearch through a prop - i.e.
export default class Home extends Component {
constructor (props) {
super(props);
this.state = { customersData: null };
this._getAllCustomers = this._getAllCustomers.bind(this)
}
render() {
return (
<JumboButton
onPress={() => {
this.props.navigator.push({
component: props =>
<CustomerSearch customersData={this.state.customersData} {...props} />
});
}}
>
);
}
_getAllCustomers(limit, sortAttr, order) {
apiCall.(apiUrl, {
...
}).then((responseData) => {
const customersDataAll = responseData.data;
const customersData = customersDataAll.filter((f) => {
return f.lastname !== ''
});
this.setState({ customersData });
});
}
}
Not sure how your JumboButton's onPress prop works exactly, but you should get the idea?
And in answer to 2. - Yes I would imagine this is the problem!
Given this component :
import React, { Component } from 'react';
import TrackerReact from 'meteor/ultimatejs:tracker-react';
export default class SubscriptionView extends TrackerReact(Component) {
constructor(props) {
super(props);
let params = props.params || [];
if (!Array.isArray(params)) {
params = [params];
}
this.state = {
subscription: {
collection: Meteor.subscribe(props.subscription, ...params)
}
};
}
componentWillUnmount() {
this.state.subscription.collection.stop();
}
render() {
let loaded = this.state.subscription.collection.ready();
if (!loaded) {
return (
<section className="subscription-view">
<h3>Loading...</h3>
</section>
);
}
return (
<section className="subscription-view">
{ this.props.children }
</section>
);
}
};
And another component :
import SubscriptionView from './SubscriptionView.jsx';
export const Foo = () => (
<SubscriptionView subscription="allFoo">
<SubscriptionView subscription="singleBar" params={ 123 }>
<div>Rendered!</div>
</SubscriptionView>
</SubscriptionView>
);
The first Subscription is re-rendered when the data is available, however the second one is rendered only once and nothing more. If I place a console.log(this.props.subscription, ready); inside the render function of SubscriptionView, I see
allFoo false
allFoo true
singleBar false
and that's it.
On the server side, both publish methods are
Meteor.publish('allFoo', function () {
console.log("Subscribing foos");
return Foos.find();
});
Meteor.publish('singleBar', function (id) {
console.log("Subscribing bar", id);
return Bars.find({ _id: id });
});
Both of the publish methods are being called.
Why isn't the second SubscriptionView reactive?
* Solution *
This is based on alexi2's comment :
import React, { Component } from 'react';
import TrackerReact from 'meteor/ultimatejs:tracker-react';
export default class SubscriptionLoader extends TrackerReact(Component) {
constructor(props) {
super(props);
let params = props.params || [];
if (!Array.isArray(params)) {
params = [params];
}
this.state = {
done: false,
subscription: {
collection: Meteor.subscribe(props.subscription, ...params)
}
};
}
componentWillUnmount() {
this.state.subscription.collection.stop();
}
componentDidUpdate() {
if (!this.state.done) {
this.setState({ done: true });
this.props.onReady && this.props.onReady();
}
}
render() {
let loaded = this.state.subscription.collection.ready();
if (!loaded) {
return (
<div>Loading...</div>
);
}
return null;
}
};
Then, inside the parent component's render method :
<section className="inventory-item-view">
<SubscriptionLoader subscription='singleBar' params={ this.props.id } onReady={ this.setReady.bind(this, 'barReady') } />
<SubscriptionLoader subscription='allFoos' onReady={ this.setReady.bind(this, 'foosReady') } />
{ content }
</section>
Where setReady merely sets the component's state, and content has a value only if this.state.barReady && this.state.foosReady is true.
It works!
Try separating out your SubscriptionView Components like this:
import SubscriptionView from './SubscriptionView.jsx';
export const Foo = () => (
<div>
<SubscriptionView subscription="singleBar" params={ 123 }>
<div>Rendered!</div>
</SubscriptionView>
<SubscriptionView subscription="allFoo">
<div>Rendered Again!</div>
</SubscriptionView>
</div>
);
Edit from comments conversation
Not sure if I am on the right track but you could build Foo as a 'smart' component that passes props to each SubscriptionView as required, and then use Foo as a reusable component.
Let's say that what I need to render is FooBarForm, which requires both Foos and Bars to be registered, in that specific use case. How would you do that?
You could create Components Foos and Bars that took props as required and create a parent component FooBarForm that contained those Components and passed the necessary data.
FooBarForm would handle the state and as that changed pass it to the props of its child components.
Now state is being centrally managed by the parent component, and the child components render using props passed from the parent.
The child components would re-render as their props changed depending on whether the state being passed from the parent component had changed.
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>
);
}
}