I have data of nested objects in which I am getting data of my requirement, Now I want to loop through that object and render on UI, But I am not getting Idea how to do that as the UI is fully Dynamically dependent on data.
My data
const countData = {
"current_month": {
"total_employes": 6,
"ariving": "+3",
"exiting": "-1",
"current_month": "April 2020",
"__typename": "CurrentMonthTotalEmp"
},
"previous_month": {
"total_employes": "3",
"arrived": "+2",
"exited": "-2",
"previous_month": "March 2020",
"__typename": "PrevMonthTotalEmp"
},
"__typename": "CurPrevMonthEmps"
}
to make it as array I doing this
const finalData =Object.entries(countData);
Now I want to loop this
please check my code-sandbox for full code
here in my code-sandbox I am rendering statically with HTML
Most of your React applications will use data to render a UI. That's what React excels in.
Step 1: Create a reusable component
You'll have to create a React component which receives the props for each month.
(total_employees, ariving, exiting and current_month) and renders them correctly.
for example:
const MonthComponent = ({ total_employees, ariving, exiting, current_month }) => {
//above return you can alter your data however you want using normal javascript
return (
//in 'return' you can return HTML or JSX to render your component.
<div>
<p>{total_employees}</p>
<p>{ariving}</p>
<p>{exiting}</p>
<p>{current_month}</p>
</div>
);
};
Step 2: Loop over your data and render your reusable component
Now in your parent component you can loop over your array of data.
const ParentComponent = () => {
const countData = {
"current_month": {
"total_employes": 6,
"ariving": "+3",
"exiting": "-1",
"current_month": "April 2020",
"__typename": "CurrentMonthTotalEmp"
},
"previous_month": {
"total_employes": "3",
"arrived": "+2",
"exited": "-2",
"previous_month": "March 2020",
"__typename": "PrevMonthTotalEmp"
},
"__typename": "CurPrevMonthEmps"
}
const months = Object.keys(countData); // ["current_month", "previous_month"]
return (
months.map(month => (
// map over months and render your reusable component for each month
<MonthComponent {...countData[month]} />
))
);
};
Note: Spreading over ...countData[month] is a shorthand property to pass every key-value pair of countData[month] as a prop. I could also have written:
<MonthComponent
total_employees={countData[month].total_employees}
arrived={countData[month].arrived}
exited={countData[month].exited}
previous_month={countData[month].previous_month}
/>
There is a lot of code duplication, we want to reduce that (DRY Principle). First, find the common code that abstractly describes your UI, i.e. a component that has a month/year label, some arrive/exit fields & labels, and an employee count. Convert what you want displayed to a component that takes these "standardized" props.
const MonthData = ({
arrive,
arriveLabel,
exit,
exitLabel,
totalEmployees,
month,
}) => (
<Fragment>
<label className="monthYr" align="left">
{month}
</label>
<div className="row countDiv">
<div className="col-12 col-sm-12 col-md-6 col-lg-6 col-xl-6 total">
<label className="totalHeading">Total employees</label>
<div className="totalCount">{totalEmployees}</div>
</div>
<div className="col-12 col-sm-12 col-md-6 col-lg-6 col-xl-6">
<button className="btn btn-outline-secondary button_Count form-control">
{arriveLabel}
<span className="badge badge-pill badge-primary ml-2">
{arrive}
</span>
</button>
<button className="btn btn-outline-secondary form-control">
{exitLabel}
<span className="badge badge-pill badge-primary ml-2">
{exit}
</span>
</button>
</div>
</div>
</Fragment>
);
I don't think I'd map these as you have different labeling for previous vs. current months, and you only ever display 2 months at a time. Just destructure from the countData the two months' data.
const { current_month, previous_month } = countData;
return (
<div className="row container-fluid">
<div className="form-control graphHeading"> Manpower Graph</div>
<div className="col-12 col-sm-12 col-md-12 col-lg-12 col-xl-12">
<div className="row widthContainer">
<div className="col-12 col-sm-12 col-md-6 col-lg-6 col-xl-6">
<MonthData
arrive={previous_month.arrived}
arriveLabel="arrived"
exit={previous_month.exited}
exitLabel="exited"
month={previous_month.previous_month}
totalEmployees={previous_month.total_employees}
/>
</div>
<div className="col-12 col-sm-12 col-md-6 col-lg-6 col-xl-6">
<MonthData
arrive={current_month.arriving}
arriveLabel="arriving"
exit={current_month.exiting}
exitLabel="exiting"
month={current_month.current_month}
totalEmployees={current_month.total_employees}
/>
</div>
</div>
</div>
</div>
);
You can do something like this in your JSX code:
{finalData.map(value => (
<div>{value.something}</div>
))}
you can use :
{
Object.keys(countData).map(key=>{
const month = countData[key]
return(
//you have access to month
<div>{month.total_employes}</div>
);
})
}
First, you need to convert the countData into a proper structure over which we can run our loop. to do that you need to change how you convert it to array to the following
const finalData = Object.values(countData)
After doing so we can now loop over the finalData variable using a map function like this.
{finalData.map(data => (
<div>{data.total_employes}</div>
<div>{data.ariving}</div>
))}
Moreover to handle missing key/values in the object you can do the following
{finalData.map(data => (
<div>{data.total_employes ? data.total_employes : 'NA'}</div>
<div>{data.ariving ? data.ariving : 'NA'}</div>
))}
Hope this helps
Related
Im making a small school project with a react frontend showing different characters from tv shows/movies.
I printed these chars out into some tabels, fetched from 3 different APIs (Harry Potter, Star Wars and GoT)
On the frontpage of the site i want to have a search bar function where you can search for characters in all 3 apis and show them in a table/modal (doesnt matter for now) Therefor i combined the 3 APIs into one endpoint containing them all in an array
Im having some troubles making this search bar function. I want to have a search bar where can write part of the name of a character and it will suggest or show a character based on that (ex. Harr should show/suggest Harry Potter)
So far my code looks like this:
export default function Search({ searchingForChar }) {
const init = [{ name: "", fullName: "" }];
const [allCharacters, setAllCharacters] = useState(init);
const fetchData = () => {
console.log("test");
if (searchingForChar !== "" || searchingForChar !== undefined) {
searchFacade
.searchForAllChars()
.then((data) =>
setAllCharacters([
...data.swList.results,
...data.hpList.hpDTOList,
...data.gotList.results,
])
);
}
};
useEffect(fetchData, [searchingForChar]);
return (
<>
{allCharacters
.filter((char) => char.name.includes(searchingForChar))
.map((filteredPerson) => (
<>
<h1>Her burde stå et navn: {filteredPerson.name}</h1>
</>
))}
</>
);
}
And is used in the Form here:
export default function Home() {
const [value, setValue] = useState(),
onInput = ({ target: { value } }) => setValue(value),
onFormSubmit = (e) => {
e.preventDefault();
console.log(value);
setValue();
};
return (
<div className="container-fluid padding">
<img className="logo" src={Dachma} alt=""></img>
<div className="row">
<div className="col-3"></div>
<div className="col-6 text-center">
<h4 className="mt-5">Search for your favorite character</h4>
<Form onSubmit={onFormSubmit}>
<Form.Control onChange={onInput} placeholder="Search here.." />
<Form.Text className="text-muted">
Type character name, movie, tv show etc.
</Form.Text>
<MDBBtn
outline
color="primary"
rounded
size="m"
type="submit"
className="mr-auto"
>
Search
</MDBBtn>
</Form>
<Search searchingFor={value} />
</div>
<div className="col-3"></div>
</div>
<div className="row">
<div className="col-2"></div>
<div className="col-8">
<h4 className="mt-5 text-center">Current shows in the site:</h4>
<p className="mt-2 text-muted text-center">
Click to get info about a movie/tv show.
</p>
<div className="flexDirection: row justifyContent: space-between">
<Card imgToDisplay={gotImg} />
<Card imgToDisplay={hpImg} />
<Card imgToDisplay={swImg} />
</div>
</div>
<div className="col-2"></div>
</div>
</div>
);
}
I have tried many different ways found here on stack and other places, i just cant seem to getting it to work as intended
Ive had many different errors but for now im stuck on "Cannot read property 'includes' of undefined.
Can anyone point me in the right direction? Please know that im a beginner to react/JS, so please explain if you can help.
I have a form that adds a new line on a button click. The new line must check for logic independently. In this case, it's chacking the first 2 digits of a barcode and associating it to a data set to see if it matches and return the appropriate value or "nothing found". I can't seem to get this right. First, it's not really evaluating at all. It only gives me "No Agency found" and second, it's doing it for all fields (because they all have the same v-model on a new line add). How can I achieve this so that it evaluates correctly and independently from each other?
Here's the relevant code in my template and script
<div id="q-app" class="q-pa-lg">
<div class="col-6">
<div v-for="(barcodefield, index) in barcodefields" :key="index">
<div class="flex q-pt-lg">
<div class="row flex-center">
<div class="col-3">
<div class="column">
<div class="row q-pr-lg items-center">
<label class="text-weight-medium">Starting Roll #:</label>
<q-input outlined square dense maxlength="24"
v-model.trim="barcodefield.start" ref="bcentry"></q-input>
</div>
</div>
</div>
<div class="col-3">
<div class="column">
<div class="row q-pr-lg items-center">
<label class="text-weight-medium">Ending Roll #:</label>
<q-input outlined square dense maxlength="24"
v-model.trim="barcodefield.end" #blur="showAgencyName" ref="bcentry"></q-input>
</div>
</div>
</div>
<div class="col-5">
<div class="column">
<label class="text-weight-medium">
Agency:
</label>
<div v-if="agencyName" style="min-height: 40px">
{{ agencyName }}
</div>
<div v-else style="min-height: 40px"></div>
</div>
</div>
<div class="col-1">
<div class="block float-right">
<q-btn v-if="index + 1 === barcodefields.length" #click="addLine" icon="add" color="primary" round />
<q-btn v-else style="min-width: 42px"/>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
export default {
data() {
return {
barcodefields: [],
barcodeprefixes: {
"10": "Boston",
"11": "New York",
"13": "Houston",
"14": "Connecticut",
"16": "SIA",
"17": "Colorado",
"18": "Chicago",
"19": "Washington",
},
barcodefield: {
star: "",
end: ""
},
agencyName: "",
};
},
methods: {
addLine() {
this.barcodefields.push({
start: null,
end: null
});
},
showAgencyName() {
var str = this.barcodefield.end;
var res = str.substring(0, 2);
if (this.barcodeprefixes[res] == undefined) {
this.agencyName = "Agency not found";
} else {
this.agencyName = this.barcodeprefixes[res];
}
},
},
mounted() {
this.addLine();
}
}
Here is a codepen for you.
There are several things going on here:
First, as Simon points out, don't name loop variables the same thing as a top-level data element. Instead of <div v-for="(barcodefield, index) in barcodefields" :key="index">, do <div v-for="(item, index) in barcodefields" :key="index">. Then update all the barcodefield.start and barcodfield.end references to item.start and item.end.
Then, you need to get each item to have its own agencyName, instead of all of them referring to the same data.
Update showAgencyName to this:
showAgencyName(item) {
var str = item.end;
var res = str.substring(0, 2);
if (this.barcodeprefixes[res] == undefined) {
item.agencyName = "Agency not found";
} else {
item.agencyName = this.barcodeprefixes[res];
}
},
Then you can call it like this: #blur="showAgencyName(item)"
And use it in the html like so:
<div v-if="item.agencyName" style="min-height: 40px">
{{ item.agencyName }}
</div>
(And you can get rid of the top-level barcodefield in the data object, because it's not used anymore.)
Fiddle here:
https://jsfiddle.net/ebbishop/7r1pqx9f/
First you should change name of the for loop variable named "barcodefield", beacause you already have one in your data structure
Second, i would personnaly use a function {{ getAgencyName(b) }} instead of {{ agencyName }} otherwise you will have same agency name for all lines
There are a couple of problem with this.
First, you have a typo in the barcodefield data object. You have "star" instead of "start".
Secondly in the showAgency method you are referencing the this.barcodefield properties but that doesn't exist.
What you can do is pass the index of the barcodefield to the showAgencyName method, and use that inside the method to get the desired barcodefield from the barcodefields array.
In your html:
<q-input outlined square dense maxlength="24"
v-model.trim="barcodefield.end" #blur="showAgencyName(index)" ref="bcentry"></q-input>
and the showAgencyName method:
showAgencyName(index) {
const barcodefield = this.barcodefields[index]
var str = barcodefield.end;
var res = str.substring(0, 2);
if (this.barcodeprefixes[res] == undefined) {
this.agencyName = "Agency not found";
} else {
this.agencyName = this.barcodeprefixes[res];
}
}
UPDATE:
There is another problem that I didn't notice at first. The agencyName is overwritten every time you add a new barcodefield since it is kind of a global value.
I update the Codepen with the simplest solution I could think of. Return the name of the agency from the showAgencyName and use that to print it on the interface. There are many possible other solutions to this (for example add the name the the barcodefields object in the array).
Here is a working Codepen
Newbie dev learning React.
I'm trying to create an upvote functionality to a blog post in React but when I click on the upvote button I'm upvoting all of the blog post cards at once instead of the individual card.
How can I fix this? I believe the issue may be in the way I'm setting setState? But I may be wrong and looking for help.
Thanks in advance!
====
class Posts extends Component {
state= {
points: 0
}
componentDidMount() {
this.props.fetchPosts()
}
UNSAFE_componentWillReceiveProps(nextProps) {
if (nextProps.newPost) {
this.props.posts.unshift(nextProps.newPost);
}
}
handleClick = () => {
this.setState({points: this.state.points + 1})
}
render() {
const postItems = this.props.posts.map((post, index) => (
<div key={index} className="ui three stackable cards">
<div className="ui card">
<div className="content">
<div className="header">{post.title}</div>
<div className="meta"> {post.author}</div>
<div className="description">
<p>{post.body}</p>
</div>
</div>
<div className="extra content">
<i className="check icon"></i>
{this.state.points} Votes
</div>
<button className="ui button"
type="submit"
onClick={this.handleClick}>Add Point</button>
</div>
</div>
))
return (
<div>
<br />
<h2 className="ui header">
<i className="pencil alternate icon"></i>
<div className="content">
Blog Feed
<div className="sub header">Create New Post!</div>
</div>
</h2>
{postItems}
</div>
)
}
}
You have a single component storing the "points" state for all your posts. To achieve the functionality you described, each post should be it's own component with it's own state.
class Post extends Component {
state = {
points: 0
}
handleClick = () => {
this.setState({points: this.state.points + 1})
}
render = () =>
<div key={index} className="ui three stackable cards">
<div className="ui card">
<div className="content">
<div className="header">{this.props.title}</div>
<div className="meta"> {this.props.author}</div>
<div className="description">
<p>{this.props.body}</p>
</div>
</div>
<div className="extra content">
<i className="check icon"></i>
{this.state.points} Votes
</div>
<button className="ui button"
type="submit"
onClick={this.handleClick}>Add Point</button>
</div>
</div>
}
}
You are upvoting every card because you have only one counter. A separate counter should be defined for every card.
state = {}; // dictionary-a-like state structure
handleClick = (id) => () => {
this.setState((prevState) => ({
[id]: prevState[id] ? prevState[id] + 1 : 1, // check and increment counter
}));
}
onClick={this.handleClick(post.id)} // call function with post.id as argument
{this.state[post.id] || 0} Votes // display votes for every card
Note: I assumed that every card has it's own unique id, if not - index may come handy too.
You will need one counter for each post. Currently you only have a single counter for all posts, which means that they all display that same value.
The best way to achieve this would probably be to separate your post into its own component, and have that keep track of the counter.
The following solution uses a post ID (if you have it) to create a key in a stateful points object. Then, on click, you can add to the correct points key.
state = {
points: {}
}
handleClick = postId => {
this.setState({
points: {
...this.state.points,
[postId]: (this.state.points[postId] || 0) + 1
}
})
}
const postItems = this.props.posts.map((post, index) => (
...
<div className="extra content">
<i className="check icon"></i>
{this.state.points[post.id] || 0} Votes
</div>
<button
className="ui button"
type="submit"
onClick={() => this.handleClick(post.id)}
>
Add Point
</button>
...
)
I am trying to show a three or four response items from a mapped out response on the same row. I have looked at other stack overflows but their questions were slightly different and I didnt know how to implement the example to mine.
I have tried to use both bootstrap as well as css materialize grids to do the job. However the documentary doesnt show for mapping examples which I have. Also have tried to add className of "row" to parent div and the className of "col" to children divs just as docs have said. But it just repeats the same item over and over again. I want each individual item to display just once.
import React from "react";
import axios from "axios";
import LoadingIndicator from "./loadingIndicator"
const API_KEY="something";
const API_KEY2="something1";
const API_KEY3="something2"
class Recipes extends React.Component {
state= {
ingredients:[this.props.ingredients],
loaded:false
}
handleChange=(e)=> {
this.setState({
[e.target.id]: e.target.value
})
}
handleSubmit=(e)=> {
e.preventDefault();
console.log(this.state);
axios.get(`https://www.food2fork.com/api/search?key=${API_KEY3}&q=${this.state.name}`)
.then(res=> {
console.log(res)
this.setState({
ingredients:res.data.recipes,
})
})
}
render() {
const recipeList = this.state.ingredients.length >0? this.state.ingredients.map((ingredient)=> {
return(
<div className="row" key={ingredient.recipe_id}>
<div className="col s12 m6 l3">
<img className="recipeImage" src={ingredient.image_url}/>
<p>{ingredient.title}</p>
</div>
</div>
)
}) : <LoadingIndicator loaded={this.state.loaded}/>
return(
<div style={{padding:50}}>
<form className="form" onSubmit={this.handleSubmit}>
<label>Food Name or Ingredient: </label>
<input
id="name"
onChange={this.handleChange}
className="formText"
type="text"
placeholder="type in ingredient or recipe"
/>
<button className="btn waves-effect waves-light" type="submit" name="action">Submit</button>
</form>
<div style={{marginTop:100}}>
{recipeList}
</div>
</div>
);
}
}
export default Recipes;
So so far I have gotten it to show, but with the same response item just repeated three times in the same row. I want each individual item to show just once on each row
The map function has an optional argument for index which can be used to track the current index of the item in the iteration, as well as an argument for the array itself that is being iterated.
What you essentially want to do, is create the div.row opening tag on the start of every 0th, 3rd, 6th... and so forth iterations. You also want to close the tag on every 2nd, 5th, 8th iterations. So you move that code into an if condition using the index attribute.
this.state.ingredients.map((ingredient,i, ingredientList) => {
if (ingredientList.length == 1) { //if only one item in the list add starting and closing tags
return (
<div className="row" key={ingredient.recipe_id}>
<div className="col s12 m6 l3">
<img className="recipeImage" src={ingredient.image_url}/>
<p>{ingredient.title}</p>
</div>
</div>
)
else if (i%3 === 0){
return ( //add opening tag at every multiple of 3
<div className="row" key={ingredient.recipe_id}>
<div className="col s12 m6 l3">
<img className="recipeImage" src={ingredient.image_url}/>
<p>{ingredient.title}</p>
</div>
)
else if(i%3 === 2 || i === ingredientList.length-1){//add closing tag if last item or 2nd, 5th, 8th...
return (
<div className="col s12 m6 l3">
<img className="recipeImage" src={ingredient.image_url}/>
<p>{ingredient.title}</p>
</div>
</div>
)
else { //Only add element otherwise
return (
<div className="col s12 m6 l3">
<img className="recipeImage" src={ingredient.image_url}/>
<p>{ingredient.title}</p>
</div>
)
}
});
I have an array full of objects that I fetched and I am trying to sort the array onClick of an arrow in React. I have a sort function that works perfect in javascript but I am new to React and can't figure out how to implement the function and render the list again.
I am getting errors messages of all sorts depending on what I try. Anything from cannot sort undefined to 'expecting onclick to be a function instead of an object like in this case.
var LeaderBoard = React.createClass({
sortDescending: function(property) {
return function (a,b) {
return (a[property] > b[property]) ? -1 : (a[property] < b[property]) ? 1 : 0;
}
},
getInitialState: function() {
return {
data: loading
};
},
componentWillMount: function() {
fetch('https://fcctop100.herokuapp.com/api/fccusers/top/recent', {
method: 'get'
}).then(response => response.json()).then(data => {
this.setState({
data: data,
});
}).catch(function(error) {
console.log("error is ", error);
});
},
render: function() {
var information = [];
for (var j=0; j<13; j++) {
information.push(
<div className="row" key={this.state.data.username}>
<div className="col-md-1 col-xs-1">
<h4>{j+1}</h4>
</div>
<div className="col-md-4 col-xs-4">
<h4>{this.state.data[j].username}</h4>
</div>
<div className="col-md-4 col-xs-4">
<h4>{this.state.data[j].recent}</h4>
</div>
<div className="col-md-3 col-xs-3">
<h4>{this.state.data[j].alltime}</h4>
</div>
</div>
);
}
return (
<div>
<div id="Title" className="row">
<h1>freeCodeCamp Leaderboard</h1>
</div>
<div className="row">
<div className="col-md-1 col-xs-1">
<h4>#</h4>
</div>
<div className="col-md-3 col-xs-3">
<h4>Camper Name
</h4>
</div>
<div className="col-md-5 col-xs-5">
<h4>Points in past 30 days
<img className="arrow" src="https://cdn4.iconfinder.com/data/icons/ionicons/512/icon-arrow-up-b-128.png" />
<img className="arrow" onClick = {this.state.data.sort(this.sortDescending("recent"))}
src="https://cdn4.iconfinder.com/data/icons/ionicons/512/icon-arrow-down-b-128.png" />
</h4>
</div>
<div className="col-md-3 col-xs-3">
<h4>All time points</h4>
</div>
</div>
<div>{information}</div>
</div>
);
}
});
You are setting onClick to tbe result of a function, not tbe function itself.
Replace
`<img className="arrow" onClick={this.state.data.sort(this.sortDescending("recent"))} src="https://cdn4.iconfinder.com/data/icons/ionicons/512/icon-arrow-down-b-128.png" />`
With
<img className="arrow" onClick = {() => this.state.data.sort(this.sortDescending("recent"))} src="https://cdn4.iconfinder.com/data/icons/ionicons/512/icon-arrow-down-b-128.png" />
React onClick events cannot be anything but a function, for future reference. (Also, JS refers to Arrays sometimes as Objects, which explains the error you're getting)