Unable to access nested data - reactjs

I noticed I cannot access to a nested js object.
this.DB.get(2) returns an object in the form
{
id:1,
name: "my title",
Obj:{en:"english",fr:"french"}
}
This is the component
export default class Item extends Component {
state = {
Item:{}
}
constructor(props) {
super(props);
}
componentDidMount(){
this.DB = $DB()
this.setState({
Item: this.DB.get(2)
})
}
render() {
const { params } = this.props.navigation.state;
const id = params ? params.id : null;
const Item = this.state.Item
return (
<View style={{flex:1}}>
Number field: {Item.id} |
String field: {Item.name} |
Object field: <FlatList data={Object.entries(Item.Obj)} />
</View>
);
}
}
The problem: I cannot access to Item.Obj.
I got it, setState is async, I'm not sure this is causing the issue though. Anyway: is there a clean way for render the component in setState callback?
edit: I've just rebuilt (still in debug mode) and now it works, I do not changed anything, just added some console.log() around.
Anyway, I don't feel so safe. setState() is async and still I see around the web this function used wildly as it would be sync.
If the data I want to render are in the state, is there a way to render the component always after the state update?
Another doubt: you see the Component, it just does an access to a big JS object and shows some data and that's all. Do I really need to pass through the component state?
What do you think if I would move those 2 lines inside the render() method?
const DB = $DB()
const Item = DB.get(2)

Related

How to use a callback in a render() of React component

I want to use the result of a server method that does a count on a collection to show it in the render method of a react component. I understand that I must do it from the callback function but it doesn't work for me.
Server Method:
export const analysis = new ValidatedMethod({
name: "analysis",
validate: new SimpleSchema({
codigo: {
type: String
},
rta: {
type: String
}
}).validator(),
run(one) {
const rta = Respuesta.find({
codigo: one.codigo,
rtatexto: one.rta,
activo: true
}).count();
console.log(rta);
return Number(rta);
}
});
Call from client:
export default class AnalysisFila extends Component {
constructor(props) {
super(props);
}
render() {
const one = { codigo: this.props.codigo, rta: this.props.opcion };
const rta = analysis.call(one, (err, res) => {
return (
<Table.Row>
<Table.Cell> {this.props.opcion} </Table.Cell>
<Table.Cell> {res} </Table.Cell>
</Table.Row>
);
});
}
}
How do I use the value of res in my component's render method?
Asynchronous Functions
Before I answer the question, it is important to understand the difference between synchronous and asynchronous functions in JavaScript.
Therefore, if you are new to JavaScript or asynchronous behaviours, I recommend you read up on this excellent answer and explanation about the topic
Explanation for the Problem
The problem here is that React's render() method is a synchronous method, meaning React expects a synchronous return value with jsx (or a React.Element for that matter).
For React, your current render function actually returns nothing or undefined, as you do not a have a synchronous return statement. And so nothing will get rendered.
React has no way of knowing when your callback is called or what it returns, as it is executed completely out of Reacts context.
Solution to the Problem
The way to go here is to use Reacts state which is exactly for these kind of scenarios.
By using state and the method setState(), you can asynchrounously trigger the render-method to be called again as soon as the response from your API delivered the data. Also, with this.state, you can bring the response-data into the react-context and let it know about it.
A full example could look like this:
export default class AnalysisFila extends Component {
constructor(props) {
super(props);
this.state = {
loaded: false
opcion: {}
};
}
componentDidMount() {
const one = { codigo: this.props.codigo, rta: this.props.opcion };
const rta = analysis.call(one, (err, res) => {
this.setState({
loaded: true,
opcion: res
}
});
}
render() {
const content = this.state.loaded ? this.state.opcion : 'Loading...';
return (
<Table.Row>
<Table.Cell> {this.props.opcion} </Table.Cell>
<Table.Cell> {content} </Table.Cell>
</Table.Row>
);
}
}
I also used the componentDidMount-Lifecycle-Method of React, as it is the recommended way to trigger API Calls. You can read more about Lifecycles in the official lifecycle documentation
The Life-Cycle of this Component will be like the following:
Component will get created and constructor will set an initial value for this.state
render() method is called with this.state.loading === false - thus the content will default to the string 'loading...'
The componentDidMount() function gets called and will trigger your API-Call
Your callback is called and executes setState(), putting the response-data into this.state
Now that react knows the state was changed, it calls the render() method again. But this time, this.state.loading === true and there is some value for this.state.opcion - thus the content will be the value from your response.

Why won't parent props get updated in dynamically created child Components (that are generated once)

Playing with performance and seeing if saving the state of dynamically generated components on componentDidMount() essentially creates performance improvement.
I see that {...this.props} only represents the props passed on the initial creation. Updates to the parent props does not trickle down on render() update.
This makes sense given React architecture but doesn't given JS referencing. Is it because this.props is merely an update of props, not an actual variable reference?
Musings: This would seem most performant method of dynamic components because they are generated only once. But it loses React's tracking ability I guess, why wouldn't React maintain understanding that these Views are grabbing {..this.props}.
// Inline Render
class MyDynamicViews extends React.Component {
state = {
views: []
}
componentDidMount() {
let json = [{
view: 1
}, {
view: 2
}, {
view: 3
}];
let views = json.map((view, i) => {
return <View key={i} {...this.props}>{view.i}</View>
});
this.setState({views});
}
render() {
return <View>{this.state.views}</View>
}
}
views returns <View key={i} {...this.props}>{view.i}</View> and you're setting this element using setState. This is not correct way to do. You would need to do like below:
class MyDynamicViews extends React.Component {
state = {
views: []
}
componentDidMount() {
// now, you want to update the state
// I suppose you're going to update the state fetching the data
// thus, placed your new state data same here
let views = [{
view: 1
}, {
view: 2
}, {
view: 3
}];
this.setState({views});
}
render() {
const views = this.state.views
return views && views.map((view, i) => (
<View key={i} {...this.props}>{view}</View>
))
}
}
Now, you'll get your component working fine and without any issue with props.

React.js, correct way to iterate inside DOM

Im new in ReactJS...
I have a project with the following class components structure:
index.js
--app
--chat
--header
--left
--right
In the chat.js component, I make a google search with the api to retrieve images based on specific keyword... My intuitive solution was:
this.client.search("cars")
.then(images => {
for(let el of images) {
ReactDOM.render(<img src="{{el.url}}" syle="{{width: '100%'}}" />, document.querySelector('#gimages'));
}
});
It is correct? Or I may to use Components with stored states with flux (redux)?
Perhaps a simpler more conventional use of react would achieve what your require?
You could follow a pattern similar to that shown below to achieve what you require in a more "react-like" way:
class Chat extends React.Component {
constructor(props) {
super(props)
this.state = { images : [] } // Set the inital state and state
// model of YourComponent
}
componentDidMount() {
// Assume "client" has been setup already, in your component
this.client.search("cars")
.then(images => {
// When a search query returns images, store those in the
// YourComponent state. This will trigger react to re-render
// the component
this.setState({ images : images })
});
}
render() {
const { images } = this.state
// Render images out based on current state (ie either empty list,
// no images, or populated list to show images)
return (<div>
{
images.map(image => {
return <img src={image.url} style="width:100%" />
})
}
</div>)
}
}
Note that this is not a complete code sample, and will require you to "fill in the gaps" with what ever else you have in your current Chat component (ie setting up this.client)
This is not the way you should go, you don't need to use ReactDOM.render for each item. Actually, you don't need to use ReactDOM.render at all. In your component you can use a life-cycle method to fetch your data, then set it to your local state. After getting data you can pass this to an individual component or directly render in your render method.
class Chat extends React.Component {
state = {
images: [],
}
componentDidMount() {
this.client.search( "cars" )
.then( images => this.setState( { images } ) );
}
renderImages = () =>
this.state.images.map( image => <Image key={image.id} image={image} /> );
render() {
return (
<div>{this.renderImages()}</div>
);
}
}
const Image = props => (
<div>
<img src={props.image.url} syle="{{width: '100%'}}" />
</div>
);
At this point, you don't need Redux or anything else. But, if you need to open your state a lot of components, you can consider it. Also, get being accustomed to using methods like map, filter instead of for loops.

How to force Apollo Query component to re-run query when parent component re-renders

I'm using Apollo Client's <Query> within a component that is re-rendered when state is changed within a lifecycle method. I wish to have my <Query> component re-run the query because I know that data has changed.
It appears that Query component is using a cache that needs to be invalidated before query is re-run.
I'm using a wonky workaround that caches the refetch callback from the render prop in the parent component, but it feels wrong. I'll post my approach in the answers if anyone is interested.
My code looks something like this. I removed loading and error handling from query as well as some other detail for brevity.
class ParentComponent extends React.Component {
componentDidUpdate(prevProps) {
if (this.props.refetchId !== prevProps.refetchId) {
const otherData = this.processData() // do something
this.setState({otherData}) // this forces component to reload
}
}
render() {
const { otherData } = this.state
return (
<Query query={MY_QUERY}>
{({ data }) => {
return <ChildComponent gqlData={data} stateData={otherData} />
}}
</Query>
)
}
}
How do I force <Query> to fetch new data to pass to <ChildComponent>?
Even though ParentComponent re-renders when props or state change, Query doesn't re-run. ChildComponent gets an updated stateData prop, but has a stale gqlData prop. As I understand Apollo's query cache need to be invalidated, but I'm not sure.
Please note that passing refetch to ChildComponent is not the answer because it only displays information from GraphQL and wouldn't know when to refetch. I don't want to introduce timers or otherwise complicate ChildComponent to solve this - it doesn't need to know about this complexity or data fetching concerns.
I had almost a similar situation which I solved with fetchPolicy attribute:
<Query fetchPolicy="no-cache" ...
The task was to load some details from server by clicking on a list item.
And if I wanted to add an action to force re-fetching the query (such as modifying the details), I first assigned the refetch to this.refetch:
<Query fetchPolicy="no-cache" query={GET_ACCOUNT_DETAILS} variables=... }}>
{({ data: { account }, loading, refetch }) => {
this.refetch = refetch;
...
And in the specific action that I wanted the refetch to happen, I called:
this.refetch();
In addition to the already accepted answer above there are 2 ways of achieving this.
If all you need is getting data from the graphql server to generate a list every time a component is mounted you have these options:
Use the option fetchPolicy="no-cache"
export default defineComponent({
setup() {
const { result, loading, error } = useMyQuery(
() => {
return { date: new Date() }
},
{
pollInterval: 15 * 60 * 1000, // refresh data every 15 min
fetchPolicy: 'no-cache',
}
)
Use the refetch() method while keeping the cache in place:
export default defineComponent({
setup() {
const { result, loading, error}, refetch } = useMyQuery(
() => {
return { date: new Date() }
},
{
pollInterval: 15 * 60 * 1000, // refresh data every 15 min
}
)
if (!loading.value) void refetch()
It seems to me that the Query component doesn't necessarily need to be inside this ParentComponent.
In that case, I would move the Query component up, since I would still be able to render other stuff while I don't have results in the ChildComponent. And then I would have access to the query.refetch method.
Note that in the example I added the graphql hoc, but you can still use Query component around <ParentComponent />.
class ParentComponent extends React.Component {
componentDidUpdate(prevProps) {
if (this.props.refetchId !== prevProps.refetchId) {
const otherData = this.processData() // do something
// won't need this anymore, since refetch will cause the Parent component to rerender)
// this.setState({otherData}) // this forces component to reload
this.props.myQuery.refetch(); // >>> Refetch here!
}
}
render() {
const {
otherData
} = this.state;
return <ChildComponent gqlData={this.props.myQuery} stateData={otherData} />;
}
}
export graphql(MY_QUERY, {
name: 'myQuery'
})(ParentComponent);
Could you refetch in parent component? Once the parent component get an update, then you can evaluate whether to trigger a fetch or not.
I have done it without using Query like the following:
class ParentComp extends React.Component {
lifeCycleHook(e) { //here
// call your query here
this.props.someQuery()
}
render() {
return (
<div>
<Child Comp data={this.props.data.key}> //child would only need to render data
</div>
);
}
}
export default graphql(someQuery)(SongCreate);
So you can trigger your fetch anytime you want it to. You can get the query as a prop in this case.
For your case, you would put your query into a prop using export default graphql(addSongQuery)(SongCreate);. Then call it in your lifecyclehooks DidUpdate.
Another options is to use refetch on Query.
<Query
query={GET_DOG_PHOTO}
variables={{ breed }}
skip={!breed}
>
{({ loading, error, data, refetch }) => {
if (loading) return null;
if (error) return `Error!: ${error}`;
return (
<div>
<img
src={data.dog.displayImage}
style={{ height: 100, width: 100 }}
/>
<button onClick={() => refetch()}>Refetch!</button>
</div>
);
}}
</Query>
The second method would require you pass something down to your child, which isn't really all that bad.
As promised, here's my hacky workaround
class ParentComponent extends React.Component {
componentDidUpdate(prevProps) {
if (this.props.refetchId !== prevProps.refetchId) {
const otherData = this.processData() // do something
this.setState({otherData})
if (this.refetch) {
this.refetch()
}
}
}
render() {
const { otherData } = this.state
const setRefetch = refetch => {this.refetch = refetch}
return (
<Query query={MY_QUERY}>
{({ data, refetch }) => {
setRefetch(refetch)
return <ChildComponent gqlData={data} stateData={otherData} />
}}
</Query>
)
}
}

Where do I call setState for redux values?

I'm pretty new to react native and async programming, and trying to understand how to "sync" redux state values and local state values.
For example, I have a text field "aboutMe" stored server side, and using mapStateToProps to place it into props:
const mapStateToProps = (state) => {
return { aboutMe: state.aboutMe };
}
In render, I have a TextInput I'm using so that the user can edit this field, and I would like to default to what is saved on the server side:
<TextInput
onChangeText={(aboutMe) => {
this.setState({aboutMe});
}}
value={this.state.aboutMe}
/>
Basically, somewhere I need to call
this.setState({ aboutMe: this.props.aboutMe });
Where is the right place to this? I was trying to use componentWillReceiveProps, but that lifecycle method is not called on constructor, so I would need to setState twice (in constructor and in componentWillReceiveProps).
Is there another way to do this? I feel like this is a pretty generic problem that a lot of react native developers have solved but I couldn't find a generally accepted way online.
Thanks!
Edit:
I have alot of TextInputs, so I have a separate button to call the action to save the variables:
<Button onPress={()=>{
this.props.saveUserInput(this.state.aboutMe,
this.state.name, this.state.address, ....}}>
<Text> Save changes </Text>
</Button>
From the comments, I understand that it's possible to call the save action onChangeText... but is that too much traffic back and forth? Would it be better to save all of the variables locally to state and then call a save for everything at once? Also, what if the user would like to "cancel" instead of save? The changes would have been already saved and we will not be able to discard changes?
1) If your component is a controlled component (you need state in it) and the request is asynchronous indeed you have to set the state in the componentWillReceiveProps like this:
class ExampleComp extends Component {
constructor(props) {
super(props);
this.state = {
aboutMe: ""
}
}
componentWillReceiveProps(nextProps) {
this.setState({
aboutMe: nextProps.aboutMe,
});
}
render() {
return (
<TextInput
onChangeText={(aboutMe) => {
this.setState({aboutMe});
}}
value={this.state.aboutMe}
/>
);
}
}
Keep in mind the key here is that the state must remain the single source of truth from now on.
2) The other option would be, you can wait until the request is finished in the parent component and then set the aboutMe in your constructor, this way you can avoid componentWillReceiveProps. For example:
class ParentComp extends Component {
render() {
return (
<div>
{this.props.aboutMe && <ExampleComp/>}
</div>
);
}
}
class ExampleComp extends Component {
constructor(props) {
super(props);
this.state = {
aboutMe: props.aboutMe
}
}
render() {
return (
<TextInput
onChangeText={(aboutMe) => {
this.setState({aboutMe});
}}
value={this.state.aboutMe}
/>
);
}
}
The downside of this is that the text input won't be shown until the request is finished.
Since you have edited your question, it is more clear what you want to achieve, so I want to address that.
You could keep the state of your controlled input elements in the component, then use the redux store to store persistent data and to populate the default values.
class Component extends React.Component {
constructor(props) {
super(props)
this.state = {
aboutMe: props.aboutMe,
... // other data
}
}
handleSubmit = (event) => {
event.preventDefault() // To prevent redirect
// Dispatch the save user input action
this.props.dispatch(saveUserInput(this.state))
}
render() {
return (
<form onSubmit={this.handleSubmit} />
<TextInput onTextChange={text => this.setState({...this.state, aboutMe: text}) />
... // input fields for other data
// Clicking this fill trigger the submit event for the form
<button type="submit">Save</button>
</form>
)
}
}

Resources