Best practice to handle data from API call and render React components - reactjs

I am somewhat new to React and looking for best practices for a particular situation within my React/Redux/Firebase PWA. Right now, the part I am concerned with is essentially a wrapper for the Yelp API.
I have a main component that queries the Yelp API when loaded (inside componentDidMount) based on user preferences received elsewhere in the app. This component also queries the API on a form submit with user inputs. It loops thru the data from both, and passes props to a child component. This view looks like a list of all the businesses received from the API. Here's what an example looks like:
"businesses": [
{
"rating": 4,
"price": "$",
"phone": "+14152520800",
"id": "E8RJkjfdcwgtyoPMjQ_Olg",
"alias": "four-barrel-coffee-san-francisco",
"is_closed": false,
"categories": [
{
"alias": "coffee",
"title": "Coffee & Tea"
}
],
"review_count": 1738,
"name": "Four Barrel Coffee",
"url": "https://www.yelp.com/biz/four-barrel-coffee-san-francisco",
"coordinates": {
"latitude": 37.7670169511878,
"longitude": -122.42184275
},
"image_url": "http://s3-media2.fl.yelpcdn.com/bphoto/MmgtASP3l_t4tPCL1iAsCg/o.jpg",
"location": {
"city": "San Francisco",
"country": "US",
"address2": "",
"address3": "",
"state": "CA",
"address1": "375 Valencia St",
"zip_code": "94103"
},
"distance": 1604.23,
"transactions": ["pickup", "delivery"]
},
// ...
],
As mentioned before, the main component passes down data to the child component, which renders each single object that list. This child component also creates a Link to another component based on the id of each business. This other component for now is simply the exact same look as one individual child component, just on a different URL. For example, the main component is "/venues" and the individual page for a business would be "/venue/E8RJkjfdcwgtyoPMjQ_Olg". The data is pulled from the Redux state (its a HOC), and filtered out to find that id.
The problem I'm running in to is when I refresh the page while on a businesses's individual page, Redux state is cleared out and hence is unable to render properly with the data. To try and work around this I attempted to have a service worker cache everything it would need, but this does not work. Refreshing ends up just showing the loading page I created, because its not making the call (which is expected - don't want it doing this) and its also not pulling data from the caches.
Is there a better way to accomplish getting data all the way to the individual business component after a refresh? Or a better way to cache the entire page/API response so it will render properly on a refresh?
I suppose I could have it reach out to the API for that specific business but I was trying to avoid that.

I remember Yelp API has an endpoint where you can get details of an individual business. That said, you should have a dynamic route for your individual business page; e.g. /businesses/:id. Then you can access this query param in your route props and go from there to fetch data for that specific business.
Something like this:
class IndividualBusiness extends Component {
// ...
componentDidMount() {
fetchBusinessById(this.props.match.id).then(setState(...))
// ...
}
// ...
}
The route would look like something like this:
<Route exact path="/businesses/:id" render={(routeProps) => <IndividualBusiness {...routeProps} />}/>
But what about when data is already in redux store? Simple, just add the control flow in your componentDidMount().

Have you tried localStorage? there's a redux middleware for it. It looks something like this:
import {createStore, compose} from 'redux';
import persist from 'redux-localstorage';
const store = createStore(reducer, compose(...otherMiddleware, persist(['apiResponses'])))
That should make the "apiResponses" section of your redux state persist through browser reloads.

Related

Property "children" is missing when retrieving synced block

Notion API has supported synced_block. I am using the Notion API with Next.js. According to the docs and the changelog, the output should contain such properties.
This is an example of an "original" synced_block. Note that all of the blocks available to be synced in another synced_block are captured in the children property.
{
"type": "synced_block",
"synced_block": {
"synced_from": null,
"children": [
{
"callout": {
"text": [
{
"type": "text",
"text": {
"content": "Callout in synced block"
}
}
]
}
}
]
}
}
So far, I can get the block_id of the original synced block from the reference synced block.
However, the children property is missing for the original synced block. I cannot get the content of the synced_block that I created.
Only the synced_from property can be retrieved.
"synced_block": {
"synced_from": null
}
Tried with Postman and get the same result.
After reaching for the support from Notion, I found that I misunderstood the API documentation.
Briefly, instead of retrieving a block, I will need another api call to retrieve the block children.
If you need to retrieve the children of the synced block you can use the "Retrieve block children" endpoint here in the documentation. This will return information on the content inside the synced block.
To retrieve children of a block using #notionhq/client:
const response = await notion.blocks.children.list({
block_id: blockId,
});

Best way to handle big dynamic form with stages in React?

I want to make an app where the admins can create "global" forms that other users can fill in. So I need these global forms to be dynamically rendered, and they are kind of big (30+ fields) and are divided in stages (e.g. stage 1 is for personal info, stage 2 is for job skills, etc).
I thought of receiving these "global" forms via JSON, something like this:
{
"filledBy":"User",
"stages":[
{
"id":1,
"name":"Personal information",
"fields":[
{
"id":1,
"type":"email",
"name":"email",
"label":"E-mail",
"placeholder":"name#company.com",
"value":"",
"rules":{
"required":true
}
},
{
"id":2,
"type":"text",
"name":"name",
"label":"Name",
"placeholder":"John Smith",
"value":"",
"pattern":"[A-Za-z]",
"rules":{
"required":true,
"minLength":2,
"maxLength":15
}
}
]
},
{
"id":2,
"name":"Job profile",
"fields":[
{
"id":1,
"type":"multi",
"name":"workExperience",
"subfields":[
{
"id":1,
"type":"text",
"name":"position",
"label":"Position",
"placeholder":"CEO",
"value":"",
"rules":{
"required":true,
"minLength":3,
"maxLength":30
}
},
{
"id":2,
"type":"date",
"name":"startDate",
"label":"Starting date",
"placeholder":"November/2015",
"value":"",
"rules":{
"required":true,
"minValue":"01/01/1970",
"maxValue":"today",
"showAsColumn":true
}
},
{
"id":3,
"type":"date",
"name":"endDate",
"label":"Ending date",
"placeholder":"March/2016",
"value":"",
"rules":{
"required":true,
"minValue":"endDate",
"maxValue":"today",
"showAsColumn":true
}
}
]
}
]
}
]
}
So I created a component called MasterForm that first gets the empty form in componentDidMount(), like a blueprint. Then, once it is fetched, it tries to get the data entered by the user and put it in the form as the value property. After that, it passes the form down to the Stage component which renders every field as an Input component. That way, MasterForm controls the current stage, and allows the user to navigate among stages, and also fetches the data and fills the form. With all the checks and stuff, my MasterForm component got very big (around 700 lines), and every time I update the value of a field in the form, I update the whole form object in the state, so I think that might be slow. Also, to fill in the form with the user's data, I have to copy every nested object and array inside the form object, to avoid mutating the state, and that's also very messy (a lot of const updatedFields = { ...this.state.form.stage.fields } and stuff).
Are there better ways to do this (preferably without Redux)? How could I decouple this huge MasterForm component? Is there a better way to update the form values (other than updating the whole form every time)? or maybe React is smart and doesn't update the whole state, but just the bit that changed... I'm not sure, I'm new to React.
Look into formik https://github.com/jaredpalmer/formik and Yup https://github.com/jquense/yup
Here they are coupled together https://jaredpalmer.com/formik/docs/guides/validation#validationschema

How to set parent component state from child of child

I am trying to finish this web challenge to land an Internship interview, and I have this problem with my code :
My webapp is a page that contains 4 main components :
- ProfilePage: Parent of all
- UserPanel : A side bar that shows user data via request to api
-TabBar : A container next to side bar that contains 2 components :
-> EventsTab : Shows events related to current user (same as the one displayed on user pane )
-> FriendsTab : Show friends of the current user (contains
"Fetch query={this.props.query} action={this.props.action}/")
-> Fetch : The component receives which call to make to the API through its query prop. It also calls the proper handler function explained below to handle different calls ( events, friends, userpanel )
My approach after a good amount of research and some coffee, was to Lift all the states Up to my ProfilePage Container, and I am now able to display user data in the UserPanel component,
I am using a handler to call a function getChildUser(user) with the user being the axios response.data directly from the Fetch Component, thus, getting the response from Fetch .
To give you a better idea, here is an example of a "/api/players/4" GET Response :
{
"id": 1,
"first_name": "Foo",
"last_name": "Bar",
"company": "Martin, Hi and Dumont",
"city_name": "Andreberg",
"last_seen": "2018-10-08T12:23:13.687Z",
"picture": "https://s3.amazonaws.com/uifaces/faces/twitter/rawdiggie /128.jpg",
"total_events": 93,
"total_friends": 83
}
And the handler :
this.getChildUser = this.getChildUser.bind(this);
this.getFriendsList = this.getFriendsList.bind(this);
//...
getChildUser(user) {
this.setState({
user: {
playername: user.first_name + " " + user.last_name,
picture: user.picture,
city: user.city_name,
last_seen: user.last_seen,
events: user.total_events,
friends: user.total_friends,
company: user.company
}
});
}
This Handler is called on the "UserPanel query="/players/5/" action={this.getChildUser}" inside of ProfilePage where we have "Fetch query={this.props.query} action={this.props.action} ", then we display the player data :
//.
<Card.Title> {this.props.user.events} </Card.Title>
//.
Finally, on our 'Fetch' :
axios
.get(API + "/" + this.props.query)
.then(response => this.props.action(response.data));
Now the I did the same thing to get using the same Fetch component, to retrieve the list of friends, but I just can't manage to get it working, i figured it has something to do with the formatting of the response because the friends response is as follows :
When calling "/api/friends/5"
[
{
"id": 18508,
"first_name": "Elisa",
"last_name": "Caron",
"company": "Carre, Rolland and Rodriguez",
"city_name": "West Louisshire",
"last_seen": "2017-11-14T09:31:52.026Z",
"picture": "https://s3.amazonaws.com/uifaces/faces/twitter/skkirilov/128.jpg",
"total_events": 193,
"total_friends": 25
},
{
"id": 92653,
"first_name": "Louis",
"last_name": "Bertrand",
"company": "Nicolas, Faure and Lemaire",
"city_name": "Port Ambretown",
"last_seen": "2018-06-22T11:14:12.862Z",
"picture": "https://s3.amazonaws.com/uifaces/faces/twitter/taybenlor/128.jpg",
"total_events": 113,
"total_friends": 135
}
]
I tried using this handler: in this case the name is getFriendsList
I realize it's a nested object field, but I couldn't figure out a way to fix it.
getFriendsList(friendsList){
this.setState({
friendsList : [
user: {
playername: friendsList.user.first_name + " " + friendsList.last_name,
picture: friendsList.user.picture,
events: friendsList.user.total_events,
friends: friendsList.user.total_friends,
},
]
})
}
This time we pull then info from "Fetch" by calling getFriendsList(response.data) trough our "FriendsTab" component then "TabBar" then back to "ProfilePage"
I get the error : _this.props.friendsList
" TypeError: _this.props.friendsList is undefined " in my component
Thank you !
I tried transforming the json response from the "Fetch" into an array, changing the friendsList structure ( though I'm not sure I've done it right )
Here is my github repository if you want to look it up more in detail
https://gitlab.com/mRamzi/mybchallenge
EDIT:
I can confirm that the problem comes from axios not correctly puttin data in my state, using react dev tools I noticed that props are passed but still empty, the getFriendsList() did not do it work, still trying to solve it
I have managed to resolve the problem, I will just go ahead and give the details in case someone faces the same issue:
The issue was indeed caused by the formatting of my json response (axios's response.data) that contained nested objects, i had to map over the friendsList and get the values.

How to avoid extra rendering in react with redux

I have next object:
Book = {
id: 1,
titel: "Some Book",
sections: [
{id: 1,
titel: "Section Titel",
pages: [
{id: 1, content: "Some text"},
{id: 2, content: "Some text"},
{id: 3, content: "Some text"}
]
},
{id: 2,
titel: "Section Titel",
pages: [
{id: 1, content: "Some text"},
{id: 2, content: "Some text"}
]
}
]
}
Book object stored in Redux store (state.Book).
I have component which visualizes this Book object.
Component Book renders one or more Section components and each Section component renders Page component.
Book subscribed to Redux over connect function from react-redux and listens to store.Book, hole object.
It is clear that when titel of the Book will change then hole Book object will be re-rendered including all Sections and Pages.
There are two questions:
Will react engine really re-render Sections and Pages if only Book.titel changed? Or it will identify that other parts of the component do not changed and will not re-render them.
If it will re-render then what is the best way to avoid it? Should I subscribe Section and Page component to the Store also? But it will not solve the problem, because Book listens to hole state.Book object. Or should I subscribe Book component only to state.Book.id and state.Book.titel, then use this.context.store inside to path data to the inner components?
Thanks in advance.
Yes, React will call the render method in Sections and Pages but don't be worried about performance as those are really inexpensive operations. The Virtual DOM will abstract all the heavy operations (i.e. manipulating the DOM) minimizing the impact on performance. You can run some quick tests yourself to see how the UX is not affected at all by changes this size.
I would not recommend to prevent re-rendering (as I said in #1 it's an unexpensive operation) but if you really want to, you could use shouldComponentUpdate in the Section components. You just need to return false whenever you feel the Section or their child components don't need to render

How does toJSON in $firebaseObject work?

I forked a normalization example from Kato's fiddle and updated it to the current version of AngularFire and Firebase, you can see it here. I tested around a bit to get a better understanding of how $firebaseObject and $firebaseArray work and I really get a hang of it.
Except the toJSON from $firebaseObject.
So I have this little code in the controller:
$scope.singlePost = singleMergedPost('post2');
console.log('singlePost', $scope.singlePost);
The output normally is:
{
"postData": {
"bla": "blubb",
"dateCreated": 1397584465,
"title": "Another cool website",
"upvotes": 1,
"url": "http://www.google.com",
"user": "simplelogin:2"
},
"userData": {
"email": "kato#firebase.com",
"jobTitle": "Awesome Dude",
"name": "Kato Richardson"
}
}
But when I add toJSON to $extend the output is:
{
"bla": "blubb",
"dateCreated": 1397584465,
"title": "Another cool website",
"upvotes": 1,
"url": "http://www.google.com",
"user": "simplelogin:2"
}
$scope.singlePost actually contains the same data, but I am wondering about:
Why is toJSON even called here, although I haven't sent back any data to the server (at least it looks like from my point of view) yet.
Why does <pre ng-bind="singlePost | json"></pre> only show the postData data?
1. toJSON
.toJSON() is called to strip the properties and methods of a $firebaseObject or $firebaseArray that start with $ or $$. It serializes the object to valid JSON data.
This is done to display the data in the <pre> div for debugging, without the $ methods.
See this answer from Kato.
2. Difference in data
<pre ng-bind="singlePost | json"></pre> only shows the postData because only the postData property of this (the $firebaseObject) is passed to the .toJSON() method in the line $firebaseUtils.toJSON(this.postData);
Compared to passing the whole object (including postData and userData properties) in console.log('singlePost', $scope.singlePost);
If you log $scope.singlePost.postData, you should see the same result.

Resources