React - populate component prop with data from HTTP endpoint - reactjs

I’m a beginner with React, working on building my first real form after taking a few online classes.
I have a form that I use to record work unit goals and the daily “score” toward those goals. The form is working fine and data is arriving properly in my database. At the bottom of the form, I want to display the running total score for today.
I’m able to pull in my component that’s supposed to display the score, but I just can’t quite figure out how to populate that score component with the actual score value. I’ve tried numerous different methods of updating the “totalToday” value in state.data so that value will then be available as a prop. So far, nothing has worked.
Here’s my form file (with quite a bit of the irrelevant other code removed). You can see at the bottom of the form that I’m pulling in the “TotalToday” component and trying to send it the “totalToday” value as a prop.
import React from "react";
import Form from "./common/form";
…
import TotalToday from "./totalToday";
class FormProject extends Form {
state = {
data: {
inpDate: "",
selTeam: "",
selGoal: "",
selProject: "",
inpScore: "",
totalToday: ""
},
teams: [],
goals: [],
projects: []
};
async componentDidMount() {
await this.populateTeams();
await this.populateGoals();
await this.populateProjects();
}
doSubmit = async () => {
await saveScore(this.state.data);
this.setState({
data: {
inpDate: "",
selTeam: "",
selGoal: "",
selProject: "",
inpScore: "",
totalToday: ""
}
});
};
render() {
return (
<div>
<h1>Team Scorecard</h1>
<form onSubmit={this.handleSubmit}>
…
{this.renderButton("Save")}
</form>
<TotalToday totalToday={this.state.data.totalToday} />
</div>
);
}
}
export default FormProject;
Here’s my “TotalToday” component file –
import React from "react";
const TotalToday = ({ totalToday }) => {
return (
<div className="alert alert-primary mt-2" role="alert">
Total Time Today: {totalToday}
</div>
);
};
export default TotalToday;
Finally, here’s my “reportService.js” file that grabs the running total from my Express / Node backend. Note that my GET request works just fine in Postman, returning today’s total, so I know the back-end setup is working, at least –
import http from "./httpService";
import { apiUrl } from "../config.json";
export function getTotalToday() {
return http.get(apiUrl + "/report");
}
And here’s the layout of the data I’m getting back from my GET request –
[
{
"scoreTotalToday": 85
}
]
As I said at the top, I’ve tried all kinds of different ways of updating the state.data.totalToday data point in my form file and haven’t been able to make it work yet. What am I missing here?
Thanks in advance for your help!
Joel

I think your first step is to use axios.
I would write it like this and do it on componentDidMount lifecycle method.
componentDidMount() {
axios.get(apiUrl + "/reports")
.then(res => {
const totalToday = res.data;
this.setState({ totalToday });
})
}
I would also change the state object to
state = {
key : "someString",
key : [someArray]
}
Also I would delete the data : in your state object, it adds another step when locating the data you are looking for. Without the api to test it can't confirm this fixes everything, but this should get you on the right track. I think missing axios is the primary fix needed.

With a key assist from Josh above, I was able to solve my own problem and now have today’s total score showing up below my form!
Here are the changes I made.
First, I really just had to come to grips with the fact that I’m getting an array back, even though it’s an array that will only ever have one element. I come from a database background, so I was just locked in on the idea that I’d have one “cell” of data, which would be the total itself. But it’s still a key-value pair array, even though it’s just a one-element array.
So, I changed my state object in the form to move “totalToday” from the “data” section into the array section where I pull in the values for my selection boxes:
state = {
data: {
inpDate: "",
selTeam: "",
selGoal: "",
selProject: "",
inpScore: ""
},
teams: [],
goals: [],
projects: [],
totalToday: []
};
I then added back to my form file a helper function that I wasn’t sure I needed before –
async updateTotalToday() {
const { data: totalToday } = await getTotalToday();
this.setState({ totalToday });
}
Which I then added to my “doSubmit” function (as well as my componentDidMount lifecycle hook) so that that total will update every time I submit a new record (note that, again, I removed “totalToday” from the “data” section of the state update) –
doSubmit = async () => {
await saveScore(this.state.data);
this.setState({
data: {
inpDate: "",
selTeam: "",
selGoal: "",
selProject: "",
inpScore: ""
}
});
await this.updateTotalToday();
};
Then, at the bottom of my form, I just sent the entire array object as a prop to my component file (so, I removed the word “data” from my previous approach) –
{this.renderButton("Save")}
</form>
<TotalToday totalToday={this.state.totalToday} />
</div>
Finally, I added array mapping to my “TotalToday” component file. This is ultimately the most important piece to the whole solution –
import React from "react";
const TotalToday = ({ totalToday }) => {
return (
<div className="alert alert-primary mt-2" role="alert">
{totalToday.map((score, index) => (
<p key={index}>Total today: {score.scoreTotalToday}</p>
))}
</div>
);
};
export default TotalToday;
Thanks again to Josh for the help. I hope my struggle through this solution helps someone else!
Joel

Related

Why is my data that is coming from apollo server not showing up when I refresh the page?

I am building a simple application using React, Apollo and React Router. This application allows you to create recipes, as well as edit and delete them (your standard CRUD website).
I thought about how I would present my problem, and I figured the best way was visually.
Here is the home page (localhost:3000):
When you click on the title of a recipe, this is what you see (localhost:3000/recipe/15):
If you click the 'create recipe' button on the home page, this is what you see (localhost:3000/create-recipe):
If you click on the delete button on a recipe on the home page, this is what you see (localhost:3000):
If you click on the edit button on a recipe on the home page, this is what you see (localhost:3000/recipe/15/update):
This update form is where the problem begins. As you can see, the form has been filled with the old values of the recipe. Everything is going to plan. But, when I refresh the page, this is what you see:
It's all blank. I am 67% sure this is something to do with the way React renders components or the way I am querying my apollo server. I don't fully understand the process React goes through to render a component.
Here is the code for the UpdateRecipe page (what you've probably been waiting for):
import React, { useState } from "react";
import { Button } from "#chakra-ui/react";
import {
useUpdateRecipeMutation,
useRecipeQuery,
useIngredientsQuery,
useStepsQuery,
} from "../../types/graphql";
import { useNavigate, useParams } from "react-router-dom";
import { SimpleFormControl } from "../../shared/SimpleFormControl";
import { MultiFormControl } from "../../shared/MultiFormControl";
interface UpdateRecipeProps {}
export const UpdateRecipe: React.FC<UpdateRecipeProps> = ({}) => {
let { id: recipeId } = useParams() as { id: string };
const intRecipeId = parseInt(recipeId);
const { data: recipeData } = useRecipeQuery({
variables: { id: intRecipeId },
});
const { data: ingredientsData } = useIngredientsQuery({
variables: { recipeId: intRecipeId },
});
const { data: stepsData } = useStepsQuery({
variables: { recipeId: intRecipeId },
});
const originalTitle = recipeData?.recipe.recipe?.title || "";
const originalDescription = recipeData?.recipe.recipe?.description || "";
const originalIngredients =
ingredientsData?.ingredients?.ingredients?.map((ing) => ing.text) || [];
const originalSteps = stepsData?.steps?.steps?.map((stp) => stp.text) || [];
const [updateRecipe] = useUpdateRecipeMutation();
const navigate = useNavigate();
const [formValues, setFormValues] = useState({
title: originalTitle,
description: originalDescription,
ingredients: originalIngredients,
steps: originalSteps,
});
return (
<form
onSubmit={(e) => {
e.preventDefault();
}}
>
<SimpleFormControl
label="Title"
name="title"
type="text"
placeholder="Triple Chocolate Cake"
value={formValues.title}
onChange={(e) => {
setFormValues({ ...formValues, title: e.target.value });
}}
/>
<SimpleFormControl
label="Description"
name="description"
type="text"
placeholder="A delicious combination of cake and chocolate that's bound to mesmerize your tastebuds!"
value={formValues.description}
onChange={(e) => {
setFormValues({ ...formValues, description: e.target.value });
}}
/>
<MultiFormControl
label="Ingredients"
name="ingredients"
type="text"
placeholder="Eggs"
values={formValues.ingredients}
onAdd={(newValue) => {
setFormValues({
...formValues,
ingredients: [...formValues.ingredients, newValue],
});
}}
onDelete={(_, index) => {
setFormValues({
...formValues,
ingredients: formValues.ingredients.filter(
(__, idx) => idx !== index
),
});
}}
/>
<MultiFormControl
ordered
label="Steps"
name="steps"
type="text"
placeholder="Pour batter into cake tray"
color="orange.100"
values={formValues.steps}
onAdd={(newValue) => {
setFormValues({
...formValues,
steps: [...formValues.steps, newValue],
});
}}
onDelete={(_, index) => {
setFormValues({
...formValues,
steps: formValues.steps.filter((__, idx) => idx !== index),
});
}}
/>
<Button type="submit">Update Recipe</Button>
</form>
);
};
I'll try to explain it as best as I can.
First I get the id parameter from the url. With this id, I grab the corresponding recipe, its ingredients and its steps.
Next I put the title of the recipe, the description of the recipe, the ingredients of the recipe and the steps into four variables: originalTitle, originalDescription, originalIngredients and originalSteps, respectively.
Next I set up some state with useState(), called formValues. It looks like this:
{
title: originalTitle,
description: originalDescription,
ingredients: originalIngredients,
steps: originalSteps,
}
Finally, I return a form which contains 4 component:
The first component is a SimpleFormControl and it is for the title. Notice how I set the value prop of this component to formValues.title.
The second component is also a SimpleFormControl and it is for the description, which has a value prop set to formValues.description.
The third component is a MultiFormControl and it's for the ingredients. This component has its value props set to formValues.ingredients.
The fourth component is also aMultiFormControl and it's for the steps. This component has its value props set to formValues.steps.
Let me know if you need to see the code for these two components.
Note:
When I come to the UpdateRecipe page via the home page, it works perfectly. As soon as I refresh the UpdateRecipe page, the originalTitle, originalDescripion, originalIngredients and originalSteps are either empty strings or empty arrays. This is due to the || operator attached to each variable.
Thanks in advance for any feedback and help.
Let me know if you need anything.
The problem is that you are using one hook useRecipeQuery that will return data at some point in the future and you have a second hook useState for your form that relies on this data. This means that when React will render this component the useRecipeQuery will return no data (since it's still fetching) so the useState hook used for your form is initialized with empty data. Once useRecipeQuery is done fetching it will reevaluate this code, but that doesn't have any effect on the useState hook for your form, since it's already initialized and has internally cached its state. The reason why it's working for you in one scenario, but not in the other, is that in one scenario your useRecipeQuery immediately returns the data available from cache, whereas in the other it needs to do the actual fetch to get it.
What is the solution?
Assume you don't have the data available for your form to properly render when you first load this component. So initialize your form with some acceptable empty state.
Use useEffect to wire your hooks, so that when useRecipeQuery finishes loading its data, it'll update your form state accordingly.
const { loading, data: recipeData } = useRecipeQuery({
variables: { id: intRecipeId },
});
const [formValues, setFormValues] = useState({
title: "",
description: "",
ingredients: [],
steps: [],
});
useEffect(() => {
if (!loading && recipeData ) {
setFormValues({
title: recipeData?.recipe.recipe?.title,
description: recipeData?.recipe.recipe?.description,
ingredients: ingredientsData?.ingredients?.ingredients?.map((ing) => ing.text),
steps: stepsData?.steps?.steps?.map((stp) => stp.text),
});
}
}, [loading, recipeData ]);

How to call an api containing array of strings through my react code

I need to call an api which consists of an array of string. I need to then publish the response from the api in a dropdown menu. Below is what the API holds that I need to call-
Sample api data - [“Leanne Graham”,”Ervin Howell”,”Patricia”]
Below sample code has the API which holds object information
import React, { Component } from "react";
import "../styles/schema.css";
import Params1 from "../components/Params1";
import axios from 'axios';
import Select from "react-select";
class New extends Component {
constructor(props) {
super(props);
this.handleStoreprocSelection = this.handleStoreprocSelection.bind(this);
this.state = {
selectStoreprocOptions : [],
id: "",
name: '',
itemSelected:false
}
}
async getStoreProcOptions(){
const resSchema = await axios.get('https://jsonplaceholder.typicode.com/users') --backend API call in object format
const data = resSchema.data
const options = data.map(d => ({
"value" : d.id,
"label" : d.name
}))
this.setState({selectStoreprocOptions: options})
}
handleStoreprocSelection(){
// alert('You selected me!!')
this.setState({itemSelected: true});
}
componentDidMount() {
// get all entities - GET
this.getStoreProcOptions()
}
render() {
const itemSelected = this.state.itemSelected;
let param;
if (itemSelected) {
param = <Params1 />;
}
return (
<div>
<div>
<form id ="store-proc">
<label>STORED PROCEDURE</label>
<Select id="store-proc-select" options={this.state.selectStoreprocOptions} onChange={this.handleStoreprocSelection} /> --my dropdown
</form>
</div>
{param}
</div>
);
}
}
export default New;
You need a state, let's say an empty array.
You need to call that API, using some of the methods, for example browser built in fetch or 3rd party library axios.
Then you need to update your state with the response you will get from your API.
Finally use your state inside of your component and display whatever you want.
These are the steps you need to follow, if you needed some logic. Since you didn't provide any code, I assume you didn't know from where to start. If you share some code, will be possible to help more.
are you using any library? because the plain HTML form select would be written in lower case <select/>, not <Select/>. if so, please state it out.
in plain HTML: the solution would be to map the array elements into <option/>. so, selectStoreprocOptions from state, as assigned here: this.setState({selectStoreprocOptions: options}).
inside render:
<select>
{
this.state.selectStoreprocOptions.map(selectStoreprocOption => (<option ..> .. </option>)
}
</select>
Edit: Sorry, I've overseen the use of react-select. never used it, according to the API doc it looks good to me. have you checked that your state really contains an array with the expected objects?
probably ignore my post then, sorry again xD

React/redux: error rendering nested object properties

I have a react/redux front-end that receives data via RabbitMQ. When a message arrives, our message handler will dispatch an action, e.g. PRODUCT_DETAILS_LOADED, that updates the store and renders the appropriate component with the product details. This is 95% working, but if I try to display nested object properties, I get an error: Cannot read property <prop> of undefined.
I have done some research on this. Most answers suggest that there's a race condition occurring, and that the component is trying to render the properties before they're "ready". I don't see how this can be the case, since I'm not making a REST call; I already have the data from the message queue and I've already updated the store. Moreover, a console.log inside the render function will show the correct values of the nested properties. Here's the bulk of my ProductDetails component:
const mapStateToProps = state => {
return { product: state.productDetail };
}
class ProductDetails extends Component {
render() {
console.log(this.props.product.availability.forsale); //This will correctly show 'true' or 'false'
return (
<div>
<h3>Product Details Page</h3>
<h5>Details for {this.props.product.name}: </h5>
<div>
Name: {this.props.product.name}
<br/>
SKU: {this.props.product.sku}
<br/>
Available: {this.props.product.availability.forsale} //This throws an error
<br/>
{/*Price: {this.props.product.price.$numberDecimal} //This would also throw an error
<br/>*/}
Description: {this.props.product.description}
</div>
<Link to="/">Back to Home Page</Link>
</div>
)
}
}
const Details = connect(mapStateToProps, null)(ProductDetails);
export default Details;
Here's a snippet from the handler that fires when a product details message is received:
} else if(json.fields.routingKey === 'product.request.get') {
if(json.status === 'success') {
let payload = json.data;
store.dispatch(productDetailsLoaded(payload));
}
Here's the function that gets dispatches the action:
export function productDetailsLoaded(details) {
return { type: PRODUCT_DETAILS_LOADED, productDetail: details }
}
And finally, the reducer:
if(action.type === PRODUCT_DETAILS_LOADED) {
return Object.assign({}, state, {
productDetail: action.productDetail
});
}
I've seen some answers that suggest something like the following to prevent the error: let av = this.props.product.availability ? this.props.product.availability.forsale : '';. This doesn't work for me. While it does prevent the error, I will display the blank value. This is not an option for me; I have to show correct information on the details page, not just "not-incorrect" information.
Can someone help me understand what's going on? Why does the console.log statement show the right data but the render statement immediately after bomb? And how can I fix this?
Thanks!
EDIT: Here's the object I'm trying to render:
{
assets: {
imgs: "https://www.zzzzzzz.com/content/12626?v=1551215311"
}
availability: {
forsale: true,
expires: "2025-12-29T05:00:00.000Z"
}
createdAt: "2015-01-25T05:00:00.000Z"
description: "his is a fake description"
modifiedAt: "2019-05-28T04:00:00.000Z"
name: "Product Name"
price: {
$numberDecimal: "59.95"
}
sku: "ZZZZ"
"title ": "Product Title"
_id: "5ceec82aa686a03bccfa67cc"
}
You are attempting to access the object before it is ready. The two places that you are having an error thrown are sub-properties. So for everything else, the initial value is "undefined" which your application is fine with, but once it tries to read a property of undefined, it will throw an error.
There are three solutions:
1) make sure the component does not load at all until the state has finished updating. This logic would be handled in the parent component of ProductDetails.
if 1) isn't an option, do one of the following:
2) set productDetails: null then in your component
render() {
console.log(this.props.product.availability.forsale); //This will correctly show 'true' or 'false'
if (!this.props.product) return <div />;
return (
// what you have above
)
}
}
const Details = connect(mapStateToProps, null)(ProductDetails);
export default Details;
3) Set a default state shape, something along the lines of:
{
assets: {
imgs: null
}
availability: {
forsale: null,
expires: null
}
createdAt: null
description: null
modifiedAt: null
name: null
price: {
$numberDecimal: null
}
sku: null
"title ": null
_id: null
}
This is part of the default behaviour of react as much as it is a redux issue. See https://reactjs.org/docs/react-component.html#setstate for more on how setState works and why you should not treat it as synchronous code.

React: setState after a graphQL request

I've been searching for a couple of hours now, but just can't seem to find the answer. See my code below. I'm requesting some metro-information to be used on an info-screen.
I'm getting the information, seeing as console.log works. However I'm having difficulty using this resulting oject. I want to use the data received, so that I can display when the next train arives. To this purpose I try to setState with the result, so that I can access the data-elements further down. However, now I'm stuck at setState giving me problems. I feel that I need to bind the function, but this.main = this.main.bind(this) doesn't work.
import React from "react";
import { GraphQLClient } from "graphql-request";
class Rutetider extends React.Component {
constructor(props) {
super(props);
this.state = {
stoppestedet: "rutetider lastes ned"
};
async function main() {
const endpoint = "https://api.entur.org/journeyplanner/2.0/index/graphql";
const graphQLClient = new GraphQLClient(endpoint, {
headers: {
ET: "lossfelt-tavle"
}
});
const query = `
{
stopPlace(id: "NSR:StopPlace:58249") {
id
name
estimatedCalls(timeRange: 3600, numberOfDepartures: 20) {
realtime
aimedArrivalTime
aimedDepartureTime
expectedArrivalTime
expectedDepartureTime
actualArrivalTime
actualDepartureTime
cancellation
notices {
text
}
situations {
summary {
value
}
}
date
forBoarding
forAlighting
destinationDisplay {
frontText
}
quay {
id
}
serviceJourney {
journeyPattern {
line {
id
name
transportMode
}
}
}
}
}
}
`;
const data = await graphQLClient.request(query);
console.log(data);
this.setState({ stoppestedet: data.stopPlace.name });
}
main().catch(error => console.error(error));
}
render() {
return (
<div>
rutetider
<div className="grid-container2">
<div>Mot byen</div>
<div>fra byen</div>
<div>{this.state.stoppestedet}</div>
</div>
</div>
);
}
}
export default Rutetider;
"probably easier" is to use integrated solution (apollo) than minimal, low level library. In most cases (as project grows), with more components fetching data managing separate GraphQLClient for all of them won't be an optimal solution. Apollo gives you centralised "fetching point", cache .. and many more.
Syntax error comes from function - in class it's enough to write async main()
https://codesandbox.io/s/l25r2kol7q
It probably would be better to save entire data in state and extract needed parts later (at render) and use this object as 'data-ready flag' (as I did for place - 'stoppestedet') - initally undefined (in constructor) for initial render (conditional rendering, some <Loading /> component):
render() {
if (!this.state.stoppestedet) return "rutetider lastes ned";
return (
<div>
rutetider
<div className="grid-container2">
<div>Mot byen</div>
<div>fra byen</div>
<div>{this.renderFetchedDataTable()}</div>
</div>

HOC/Render-Call Back or Library function?

I'm working on a project where a prospect needs to be sent an email about a property they are interested in. There is a top level component that fetches the property information and prospect's contact info from the database and passes to its children. There are two components that share the same process of formatting the information, and then call an email function that sends off an email. A sample of one component looks like this:
import sendEmail from 'actions/sendEmail'
class PropertyDetail extends React.Componet {
state = {
unit: undefined,
prospect: undefined,
};
componentDidMount = () => {
this.setState({
unit: this.props.unit,
prospect: this.props.prospect,
});
};
sendEmail = ({ id, address, prospect }) => {
// quite a bit more gets formatted and packaged up into this payload
const payload = {
id,
address,
prospectEmail: prospect.email,
};
emailFunction(payload);
};
handleEmail = () => {
sendEmail(this.state);
};
render() {
return (
<div>
<h1>{this.state.unit.address}</h1>
<p>Send prospect an email about this property</p>
<button onClick={this.handleEmail}>Send Email</button>
</div>
);
}
}
and the other component looks like this
class UpdateShowing extends React.Component {
state = {
unit: undefined,
prospect: undefined,
showingTime: undefined,
};
componentDidMount = () => {
this.setState({
unit: this.props.unit,
propsect: this.props.prospect,
showingTime: this.props.showingTime,
});
};
sendEmail = ({ id, address, prospectEmail }) => {
// quite a bit more gets formatted and packaged up into this payload
const payload = {
id,
address,
prospectEmail,
};
emailFunction(payload);
};
handleUpdate = newTime => {
// get the new date for the showing ...
this.setState({
showingTime: newTime,
});
// call a function to update the new showing in the DB
updateShowingInDB(newTime);
sendEmail(this.state);
};
render() {
return (
<div>
<p>Modify the showing time</p>
<DatePickerComponent />
<button onClick={this.handleUpdate}>Update Showing</button>
</div>
);
}
}
So I see some shared functionality that I'd love to not have to repeat in each component. I'm still learning (working my first job), and why not use this as an opportunity to grow my skills? So I want to get better at the HOC/Render props pattern, but I'm not sure if this is the place to use one.
Should I create a component with a render prop (I'd rather use this pattern instead of a HOC)? I'm not even sure what that would look like, I've read the blogs and watched the talks, ala
<MouseMove render={(x, y) => <SomeComponent x={x} y={y} />} />
But would this pattern be applicable to my case, or would I be better off defining some lib function that handles formatting that payload for the email and then importing that function into the various components that need it?
Thanks!
I think a provider or a component using render props with branching is a better fit for you here
see this doc: https://lucasmreis.github.io/blog/simple-react-patterns/#render-props

Resources