How to set parent component state from child of child - reactjs

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.

Related

updating state for a nested array (one-to-many relationship) without having to refresh page

In my project, a user may have many budgets, and each budget may have many expenses. I am using a Ruby on Rails backend and performing a fetch to localhost with my React.js frontend. I want to be able to create,update and delete for both the budgets and the expenses. So the POST request works and returns something like this :
[
{
"id": 1,
"category": "transport",
"amount": 200,
"user_id": 1,
"expenses": [
{
"id": 127,
"budget_id": 1,
"description": "transport expense",
"date": null,
"amount": 20,
"created_at": "2020-07-09T06:15:50.287Z",
"updated_at": "2020-07-09T06:15:50.287Z"
}
]
},
{
"id": 4,
"category": "a new budget",
"amount": 200,
"user_id": 1,
"expenses": []
},
{
"id": 5,
"category": "new budget",
"amount": 2000,
"user_id": 1,
"expenses": [
{
"id": 125,
"budget_id": 5,
"description": "new expense",
"date": null,
"amount": 30,
"created_at": "2020-07-09T06:15:34.533Z",
"updated_at": "2020-07-09T06:15:34.533Z"
},
{
"id": 126,
"budget_id": 5,
"description": "another new exp",
"date": null,
"amount": 30,
"created_at": "2020-07-09T06:15:43.071Z",
"updated_at": "2020-07-09T06:15:43.071Z"
}
]
}
]
My component tree looks something like this :
While I am able to delete things from the backend, items (budget/expense) don't appear to delete until I refresh the page.
I have the following function living in Budget.js in order to deduct the totals of expenses from the budgets (which should update whenever an expense is added/deleted/updated which only happens once I refresh):
handleBudgetChange = () => {
let total = 0
total = this.state.expense.reduce(
(prevValue, currentValue) => prevValue + currentValue.amount, 0);
let budgetRemaining = this.props.budget.amount - total
this.setState({
remainingBudget: budgetRemaining
})
}
I have the state of Budget living in App.js and the state of Expenses living in Budget.js (also, the state of expense is set equal to the props of the budget---- this is because I needed to have the ability to match the created expenses with the appropriate budget and for some reason this wouldn't work any other way).
If anyone has ANY TIPS whatsoever on how to fix this issue.. I've been stuck on it for days. I also am not very familiar with react (and have 0 familiarity with hooks and redux) so any help would be much appreciated. I would be happy to provide any additional info/code if needed. Thanks!
What I understood from your problem is that you want to show the updated budget/expense without refreshing the page. So here are my suggestions -
Without making an extra api call -
Handle it in the frontend - You need to update your react state once you add/update/delete an expense or budget. It doesn't involve any extra api call. You need to write the logic in the frontend to update your state. This might be hectic if you have highly nested objects.
Send updated values in the api response - You can send the updated values in your POST/PUT/DELETE api response and in the frontend, simply use this response to update your state.
Make an extra api call - After you perform an add/delete/update action (probably you are calling any PUT/POST/DELETE api), call the same GET api that you are calling to get the data initially. With this GET api response, simply update the state.
You should update your local state of budgets and expenses after getting the response from server of a CRUD operation.
Following example of code might help you
App.js
class App extends Component {
constructor() {
this.state = {
budgets: []
};
this.handleBudgetChange.bind(this);
}
render() {
return (
<div>
<Expense onBudgetsChange={this.handleBudgetChange} />
{/* rest of your code */}
</div>
);
}
handleBudgetChange(budgets) {
this.setState({
budgets: budgets
});
}
}
Expense.js
class Expense extends Component {
constructor() {
this.state = {
expenses: []
};
this.handleBudgetDelete.bind(this);
this.handleExpenseDelete.bind(this);
}
render() {
return (
<div>
{/* your render logic */}
<button onClick={this.handleBudgetDelete}>Delete Budget</button>
<button onClick={this.handleExpenseDelete}>Delete Expense</button>
{/* rest of your code */}
</div>
);
}
async handleBudgetDelete(id) {
// call api and get updated budgets list
const response = await api('/url-to-delete-budgets', id);
this.props.onBudgetsChange(response); // notify parent component to update state
}
async handleExpenseDelete(id) {
// call api to delete expense
await api('/url-to-delete-expense', id);
// update local state
this.setState({
expenses: this.state.expenses.filter(e => e.id !== id)
});
}
}

Dependent requests with rxjs error: Observable {_isScalar: false, _subscribe: f}

I'm new to Rxjs(^6.5.3). I'm using it to fetch data from api for my react app.
I am making two requests which one is dependent to the other.
I don't know what i did wrong, but i get this error:
// console ouput
Observable {_isScalar: false, _subscribe: f}
Observable {_isScalar: false, _subscribe: f}
.....
An example of how the results are shown:
// users endpoint
{
"data": {
"total": 130,
"users": [ // this format is also used as the User interface for typescript type check
{"id": 1, "name": "John Doe", "profile_url": "https://myapi.co/user/id/1"},
{"id": 2, "name": "Johny Doe", "profile_url": "https://myapi.co/user/id/2"}, ...
]
}
}
// user details endpoint
{
"data": {
"info":{"name": "John Doe", "age": 50, "gender": "male", "status": "active", ...}
}
}
Here's my code that deals with fetching data from the api
// User class
class User{
.....
private function getAllUsers(): Observable<User[]> {
return from(fetch(api.getUsers()).then(res => res.json()).then(res => res.data.users))
}
private function getUserDetails(url: string): Observable<User> {
return from(fetch(api.getUserDetails(url)).then(res => res.json()).then(res => res.data.info))
}
public function getUsers(): Observable<User[]> {
return this.getAllUsers()
.map(users => users.map(user => user.profile_url))
.flatMap(profiles => {
// console.log('flatmap: ', profiles.map(profiles => this.getUserDetails(profile)))
return r.map(x =>
this.getUserDetails(profile)
);
})
.map(users => users);
}
}
// index page
import ...
....
const userClass = new User();
userClass.getUsers()
.subscribe(users => {
console.log('users: ', users);
})
I found a similar issue Observable dependent on another observable
Update 1: replaced the the returned Observable type to Observable<User> or Observable<User[]>
I think there are a few issues here. I recommend replacing the type of Observable<any> with the type the observable is actually returning which will really help you find errors. It helps me a lot when chaining together observables. Also take(1) was my friend when they weren't completing (but I only needed the first result).
That being said, this post has some good discussion on mapping.
flatMap/mergeMap - creates an Observable immediately for any source item, all previous Observables are kept alive
concatMap - waits for the previous Observable to complete before creating the next one
switchMap - for any source item, completes the previous Observable and immediately creates the next one
exhaustMap - source items are ignored while the previous Observable is not completed
You want to wait for the outer observable - getAllUsers - to complete before starting the inner observable - getUserDetails - right? You are likely looking for concatMap or switchMap instead of flatmap.

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

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.

Access individual JSON array item and use for blog post dynamically

I'm building a simple Nuxt JS blog with a blog.json file containing an array of blog posts which contains:
Title (String)
Body (HTML markup)
Creation (Date)
I will attach the format of this shortly. I know how to iterate over each array item and display it on the page, and I also have a basic understanding and some basic experience with dynamic routing in Nuxt JS.
The problem I'm currently facing is I need to be able to access individual array items and use them as blog posts, e.g: pages/blog/_slug where _slug would be the title of a blog post, with hyphens + all lowercase automatically.
I'm wondering how I would access for instance the Winter blog post in my example and be able to go to mysite.com/blog/winter-blog-post using the following JSON format:
{
"blogs": [
{
"title": "Summer blog post",
"body": "<div class=\"post\">My blog content</div>",
"created": "2019-03-14 10:08:00"
}
{
"title": "Winter blog post",
"body": "<div class=\"post\">My blog content</div>",
"created": "2019-03-15 10:08:00"
},
{
"title": "Spring blog post",
"body": "<div class=\"post\">My blog content</div>",
"created": "2019-03-16 10:08:00"
}
]
}
I essentially want to be able to go to mysite.com/blog/winter-blog-post and have it use the content from that particular array item.
I'll assume you have your pages set up correctly and you can reach /blog/_slug, so it really is just a matter of passing the required params and converting them as needed. In blog.vue you would have a list of your posts and a click on something would navigate to the full article. That click event would trigger a method where you can manipulate the title and use it as a param. So if you have a 'Read More...' button you would assign #click="readMore(blog.title)" to that button.
Then in your methods you take the passed 'title' parameter, change it as you want, and trigger the route change.
methods: {
readeMore(title) {
let passedTitle = title.toLowerCase()
passedTitle = passedTitle.replace(" ", "-")
this.$router.push('/blog/' + passedTitle)
}
}
Then in your _slug.vue you take the passed param, change it back and use that to find your article.
export default {
asyncData({params, $axios }) {
let title = params.passedTitle.replace("-", " ")
let oldTitle = title.charAt(0).toUpperCase() + title.slice(1)
// make your query however you do, if with axios...
$axios.get('/posts', {
params: {
title: oldTitle
}
})
//or if its a vuex state item...
//let post = this.$store.state.posts.find((p) => p.title === oldTitle)
return post
},
}

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