I have React menu planning application (personal use only) which is a table of 7 columns for each day, 3 rows for lunch, dessert and dinner. In lunch and dinner row there is several dropdowns for type of meals, in dessert row there is just one column. Image attached below for better view. The meals can appear repetedly.
Now the problem - the person which is using it mostly on iPhone is complainng that when he fills the menu selecting values from dropdowns, sometimes (yes that is what I get from hit, nothing more) the app replaces values in all columns with data from one columns. The menu then looks like each week same meals. The problem is that has never occured to me though I tested the app upside down. But I test it only on Chrome - Win and Android (thought it does not seems like browser-related problem).
What I did to get to the bottom of this:
Add Error boundary: no error occured
Carefully go through key chaining in for loops in render method and make sure every dropdown has unique key
I kind of do not know which code to include. So this is part of the render method for the table:
{
TYPES.map((type, typeIndex) => (
<tr className={type === DESSERTTYPE ? "dessertRow" : ""} key={"type"+typeIndex}>
{days.map((day, dayIndex) => (
<td key={"day"+typeIndex+""+dayIndex}>
{
CATEGORIES.map((category, categoryIndex) => (
this.getMealComponent(type, category, dayIndex, "category"+typeIndex+""+dayIndex+""+categoryIndex)
))
}
</td>
))}
</tr>
))
}
And this is code to get the meal component:
getMealComponent(type, category, dayIndex, key) {
switch(type) {
case LUNCH:
case DINNER:
if(category === DESSERT) return;
return <MealsDrop key={key} keyToShow={key} selectedMenu={this.state.selectedMenu} dayTime={type} meals={this.getMeals(category)} day={dayIndex} handleMealsDropChange={this.dropChange} category={category} />;
case DESSERTTYPE:
if(category === DESSERT) {
return <MealsDrop key={key} keyToShow={key} selectedMenu={this.state.selectedMenu} dayTime={type} meals={this.getMeals(DESSERT)} day={dayIndex} handleMealsDropChange={this.dropChange} category={category} />;
}
break;
default:
return "";
}
}
I am thinking about rewriting the app because the person this app is for changed his mind about the app working several times and at the end it completely changed the flow of the app. But first I need to get to the bottom of this problem.
What else this could be the problem
structure of the selected menu could make available the mistake. But I cannot find the right place where it is. The selected menu is array of 7 items this structure:
MEALOBJECT = {
lunch: {
soup: "",
soupnote: "",
rice: "",
longcook: "",
lcnote: "",
protein: "",
pnote: "",
salad: "",
saladnote: "",
vege: "",
vnote: "",
seaweed: "",
note: ""
},
dessert: "",
dinner:{
soup: "",
soupnote: "",
rice: "",
longcook: "",
lcnote: "",
protein: "",
pnote: "",
salad: "",
saladnote: "",
vege: "",
vnote: "",
seaweed: "",
note: ""
}```
Anyone has had a problem like this? Is there a cure or should I include the link to whole project for the complex code? Thanks guys in advance to look at this. I am pointless now for looking into the code for few evenings.
Menu planning application - the table structure
Related
I know its basic but i am stuck with the following bug or error. Need help
I am trying to use simple array method to return value on console and on screen using basic methods in react but it is giving me undefined error on console.
Following are the code of React of APP.js.
import React from 'react'
function App(){
const [employees, setemployees] = React.useState([{
id: 1,
fullName: "Bob Jones",
designation: "JavaScript Developer",
gender: "male",
teamName: "TeamA"
},
{
id: 2,
fullName: "Jill Bailey",
designation: "Node Developer",
gender: "female",
teamName: "TeamA"
},
{
id: 3,
fullName: "Gail Shepherd",
designation: "Java Developer",
gender: "female",
teamName: "TeamA"
}]);
console.log(employees.fullName)
return(
<main>
<h1>{employees.fullName}<h1/>
</main>
)
}
export default App
Output:
Console: Undefined
On Screen: empty
Need help how to solve this error.
I tried different known methods like moving into components but does not work. I am expecting to show data on screen and on console.
it must be error. because you can't access employees.fullName. it must be at least employees[0].fullName to access first object inside employees array.
console.log(employees[0].fullName);
if you want to render all names as an h1 you can do this.
{employees.map(employee => <h1 key={employee.id}>{employee.fullName}</h1>)}
The error message you're encountering is likely "Cannot read property 'fullName' of undefined." This is because employees is an array of objects and not a single object, so trying to access a property like fullName directly on employees will not work.
To fix this, you need to loop over the employees array and render each individual employee. One way to do this is by using the map function to iterate over the array and create a new array of elements for each employee.
employees[0].fullName
should do it
Can you please give more details? btw, have you tried to write something like:
<div>
<ol>
{employees.map(employee => { <li key={employee.fullName}>{employee.fullName}</li>)}
</ol>
</div>
This is my first stackoverflow post....be kind
I am building a react app where I want to display an image from a array of objects but the "images:" object has two objects inside example:
[
{ value: "all", label: "All sets" },
{
value: "base1",
label: "Base",
series: "Base",
printedTotal: 102,
total: 102,
legalities: { unlimited: "Legal" },
ptcgoCode: "BS",
releaseDate: "1999/01/09",
updatedAt: "2020/08/14 09:35:00",
images: {
symbol: "https://images.pokemontcg.io/base1/symbol.png",
logo: "https://images.pokemontcg.io/base1/logo.png",
},
},]
my question is how would I access the inner object to display just the "logo:" object?
my assumption is to use a combination of .map and Object.keys() but I'm unsure how to do it.
mycode is below...
import React from "react";
import setOptions from "../data/sets";
export default function SetOverview() {
const sets = setOptions;
return (
<div>
{sets.map((set) => {
return (
<div key={set.value}>
<h2>{set.label}</h2>;<p>Number of cards: {set.printedTotal}</p>
<p>Release date: {set.releaseDate}</p>
</div>
// image to be placed here
);
})}
</div>
);```
ma friend. Welcome to Stack Overflow, and no- you will not be treated with kindness. That being said,
If you are sure about the keys that are being passed in the dataset, i.e.: objName.objItem.images.logo, then all you need to do is,
Check if the images key exists in that object (because your first object doesn't have it, so I suspect there may be a reason for that).
Load the image's logo value inside that div you've specified.
To achieve this, all you need to do is:
set.images?.logo && <img src={set.images.logo} />
And voila, you shall have your image. The question mark checks if key exists.
I have an array of questions that I'm trying to animate using Framer Motion. The shape roughly looks like:
const questionList = [
{
type: "text",
data: {
question: "What is your name?",
valid: true,
},
},
{
type: "text",
data: {
question: "How old are you?",
valid: true,
},
},
{
type: "multi",
data: {
question: "What are your favorite sports?",
responses: ["Football", "Hockey", "Tennis", "Chess"],
valid: true,
},
},
{
type: "single",
data: {
question: "What is your gender?",
responses: ["Male", "Female", "Don't know"],
valid: true,
},
},
];
I am rendering them in a list of cards using React and Tailwind like so:
export default function QuestionList() {
const [questions, setQuestions] = React.useState(questionList);
return (
<Reorder.Group values={questions} onReorder={setQuestions}>
{questions.map((question, index) => {
return (
<Reorder.Item
className="mb-4 px-4 py-6 bg-indigo-500 text-white ml-12 border-2 border-gray-100 shadow rounded-md cursor-move hover:shadow-md"
key={`question${question.type}${index}`}
value={`question${question.type}${index}`}>
{question.data.question}
</Reorder.Item>
);
})}
</Reorder.Group>
);
}
I'd like to be able to reorder the cards using Framer Motion Reorder components as described here https://www.framer.com/docs/reorder/ but every time I try the component tree crashes silently and I get a blank screen. When I reduce the questions to a flat structure like ['Question 1', 'Question 2'] etc I am able to get the re-ordering to happen. I suspected it could be something to do with the keys but playing around with that doesn't work. Grateful for any help/pointers
You shouldn't use the loop index as the key in your Reorder.Item. When you drag to reorder the item, the index (and thus the key) will change. That will make it impossible for Reorder to track which elements have been moved to where and is probably why it's crashing.
Instead use a value like a unique id property for each question. This way React (and Framer Motion) will know exactly which element was moved and can render it at the new position.
Here's a more thorough explanation:
react key props and why you shouldn’t be using index
Besides the key having to be unique, you also should set value from Reorder.Item to value={question}. If you want to generate a unique ID for each question, perhaps consider using a library such as uuidv4
I have a method in a react Contact List component where I am returning another component. I have got it working but am curious if there is a better way to structure how I am using the key.
Specifically - I'm asking about this line of code from the method below (data is hard coded as sample to get started):
return <ShortContact contact={contact} key={contact.id}/>
Here is the code in context:
_getContacts() {
let contactList = [
{
id: 1,
fName: "aaa",
lName: "aaaaa",
imgUrl: "http://brainstorminonline.com/wp-content/uploads/2011/12/blah.jpg",
email: "aaa#aaaa.com",
phone: "999999999999"
},
{
id: 2,
fName: "bbbbb",
lName: "bbbbbbb",
imgUrl: "https://media.licdn.com/mpr/mpr/shrinknp_200_200/bbb.jpg",
email: "bbb#bbb-bbb.com",
phone: "888888888888"
},
{
id: 3,
fName: "Number",
lName: "Three",
imgUrl: "http://3.bp.blogspot.com/-iYgp2G1mD4o/TssPyGjJ4bI/AAAAAAAAGl0/UoweTTF1-3U/s1600/Number+3+Coloring+Pages+14.gif",
email: "three#ccccc.com",
phone: "333-333-3333"
}
];
return contactList.map((contact) => {
"use strict";
return <ShortContact contact={contact} key={contact.id}/>
});
}
ShortContact Component Render:
class ShortContact extends React.Component {
render() {
return (
<div >
<li className="contact-short well whiteBG">
<img className="contact-short-thumb" src={this.props.contact.imgUrl}/>
<p className="contact-short-name">{this.props.contact.fName}</p><br />
<p className="contact-short-email">{this.props.contact.email}</p>
</li>
</div>
);
}
}
I struggled with how to make it work and not get the warning Warning: Each child in an array or iterator should have a unique "key" prop. However I am wondering if the syntax or structure is valid and if it should be refactored.
There is nothing wrong with this code. The key is required so that react knows how to render the children nodes. In fact your implementation is exactly what react requires the programmer to do. Now the details of which key to use and such can be changed, but it looks like you have the most performant solution already.
The main requirement is that the key is unique so as long as contact.id is always unique (which if its coming from a database then it will be) then you are fine.
Alternatively you can use an index on your map for the key but I wouldn't really recommend it (i'll explain below after the code snippet).
contactList.map((contact, i) => {
return <ShortContact contact={contact} key={i}/>
});
Personally I think your approach is the best approach because it can prevent additional renders. What I mean is for instance when a new contact is returned from the server every contact row would be re-rendered because the index in the array for each contact is different (assuming you aren't treating it like a stack)... The different index with new contact data at that index would cause the re-render. Because contact.id is a static number if the data for that contact hasn't changed then react wont re-render it.
The use of a unique key is required. In your example, using the id is ideal. see the following for more information:
https://facebook.github.io/react/docs/lists-and-keys.html
So I create a cached json object within an array with the following method in Angular:
$scope.saveClaim = function() {
//always set isOffset to false - empty string does not work for non-string objects in web api when field is required
$scope.claimInfo.isOffset = false;
$scope.claimsSubmit.push($scope.claimInfo);
//clears scope so form is empty
$scope.claimInfo = {
id: "",
benefitId: "",
isSecIns: "",
isNoResId: "",
expenseTypeId: "",
fromDate: "",
toDate: "",
provider: "",
who: "",
depId: "",
age: "",
amount: "",
comments: "",
isOffset: ""
};
}
The idea is the user fills out a form, and at the end either selects to add another claim or submit a claim (the object). After each time the form is filled and user selects file or add another, the form clears and the user then enters more data. The results is an array of object(s) that look like:
[
{
"id": "",
"benefitId": "",
"isSecIns": "",
"isNoResId": "",
"expenseTypeId": "",
"fromDate": "",
"toDate": "",
"provider": "",
"who": "",
"depId": "",
"age": "",
"amount": "",
"comments": "",
"isOffset": false
}
]
If more than one claim is entered, then we get multiple objects with same properties.
Each claim is then displayed with limited data in info boxes that display only 3-4 of the properties.
So I am trying to figure best way to do 3 things. First, add a unique "id" to each object. Second, if the delete icon in the info box selected, then remove that object from the array and if the "edit" icon is selected in the info box, then all the relative properties that that object in the array is populated back to the form.
Googling best tries for this, but not sure how I can work with the json objects this for for now. Can some of you help me on this?
Thanks much.
Hard to give the best way. Probably comes down to your style and preferences. But here is one way to do it, to get you going.
Define your model. It will contain the claim that is bound to the form and an array of added claims.
$scope.viewModel = {
claim: {},
claims: []
};
Add a function that assigns a claim object with default values:
var resetClaim = function() {
$scope.viewModel.claim = {
name: '',
city: ''
};
};
resetClaim();
The form elements will use ng-model:
<input type="text" model="viewModel.claim.name">
We will use ng-repeat to show the added claims:
<tr ng-repeat="claim in viewModel.claims">
Our form will have two buttons:
<button type="submit" ng-click="saveClaim()">Save Claim</button>
<button type="button" ng-click="cancel()">Cancel</button>
The cancel button will just reset the form.
The saveClaim function will look like this:
$scope.saveClaim = function() {
if (!isValidClaim()) return;
$scope.viewModel.claim.id ? updateClaim() : saveNewClaim();
resetClaim();
};
The isValidClaim function just checks if we have entered the requied fields. You could use form validation for this instead.
In this solution when saving a claim it could either be a new claim or an existing one that we have edited, and what we will do in the two cases will differ, so we need a way to tell what we are doing. Here we just check if it has an id. If it hasn't - it's a new claim. If it has, it's an existing.
To save a new claim we will do the following:
var saveNewClaim = function() {
var newClaim = angular.copy($scope.viewModel.claim);
newClaim.id = id++;
$scope.viewModel.claims.push(newClaim);
};
Note that it's important that we use for example angular.copy to create a new copy of the claim that is bound to the view. Otherwise we would just push a reference to the same object to the claims array which is not good since we want to reset one of them.
In this example id is just a variable starting at 0 that we increment each time we create a new claim.
Each element in our ng-repeat will have an edit and a remove icon:
<tr ng-repeat="claim in viewModel.claims">
<th>{{claim.id}}</th>
<td>{{claim.name}}</td>
<td>{{claim.city}}</td>
<td><i class="glyphicon glyphicon-edit" ng-click="editClaim(claim)"></i></td>
<td><i class="glyphicon glyphicon-remove" ng-click="removeClaim(claim)"></i></td>
</tr>
The removeClaim function simply takes a claim and removes it from the array:
$scope.removeClaim = function(claim) {
var index = $scope.viewModel.claims.indexOf(claim);
$scope.viewModel.claims.splice(index, 1);
};
The editClaim function will make a copy of the claim to edit and put it in the variable that is bound to the form:
$scope.editClaim = function(claim) {
$scope.viewModel.claim = angular.copy(claim);
};
You can also do the following:
$scope.viewModel.claim = claim;
And when you edit the claim in the form it will update in the ng-repeat at the same time. But then you have no good way of canceling and the save button wouldn't be needed. So it depends on how you want it to work.
If you edit the claim in the form now and save, we will come back to the saveClaim function:
$scope.saveClaim = function() {
if (!isValidClaim()) return;
$scope.viewModel.claim.id ? updateClaim() : saveNewClaim();
resetClaim();
};
This time the claim will have an id, so the updateClaim function will execute:
var updateClaim = function() {
var claim = $scope.viewModel.claims.filter(function(c) {
return c.id === $scope.viewModel.claim.id;
})[0];
angular.extend(claim, $scope.viewModel.claim);
};
It will retrieve the claim that we are editing from the claims array based on the id. We need to do this since we used angular.copy earlier and have two difference objects.
We will then use angular.extend to move all the new edited values to the claim that we pressed edit on in the ng-repeat.
Demo: http://plnkr.co/edit/yuNcZo7nUyxVsOyPTBEd?p=preview