React + Firebase - Deleting firebase object inside mapped components - reactjs

I'm trying to pass the key of my project object through to my mapped components in order to delete them.
UPDATE: using the _.forEach(project, (proj, key) => <Project key={key} {... more props here}/>) Throws an error about objects not being valid react children. I'm guessing this is because the project i'm forEaching over needs to be turned into an array? I keep trying to format in componentWillMount() but when i try to run forEach with setState and push to a new array i keep getting duplicates
componentDidMount() {
projectRef.on('value', snap => {
this.setState({projects: snap.val()})
// somehow i need to create a new array of objects that include the key.
})
}
UPDATE: i removed the codepen example. I like code sandbox better. Much better. =)
And here's the code sandbox (If you get an error about the [DEFAULT] app already being defined just refresh the output browser and it will work. I don't know why it's doing that... oh well. I added my attempt with forEach on the code sandbox example. Hopefully someone can let me know what i'm doing wrong.

Yep, map returns an array of the values of the object, in this case an object you can then access via the props in the <Display /> component, but not the key of each element of the object.
Perhaps you could use lodash's forEach in order to loop and have access to both the key and the value of each element in your data collection. Like that you can pass the key (that will be the target for the remove method) as a specific prop and the value as the item prop in the component.
export default class extends React.Component {
constructor() {
super()
this.state = {
items: null
}
}
render() {
return() {
<div>
{_.forEach(this.state.items, (item, key) =>
<Display key={key} itemKey={key} item={item}/>
)}
</div>
}
}
}
// then the display component
removeItem() {
firebase.database().ref({this.props.itemKey}).remove();
}
render() {
return (
<div>{this.props.item.name} <button onClick={this.removeItem}>X</button>
)
}
Here's a simple live example of how forEach works:
https://jsbin.com/xolejoj/edit?js,console
Edit 08-12-2017
The problem with your code is that you're missing the fact that map is returning the key and the value of each element of the object. In this case your object has a key string and a value that is the object. Then on your JSX you're trying to pass the key as it were a part of the object (value) but is not, therefore you're getting an undefined value in the component's props.
Change your code to this:
<div>
{_.map(projects, (proj, key) => <Project
key={key}
title={proj.title}
subtitle={proj.subtitle}
desc={proj.desc}
itemKey={key} // just the key of the object
/>
)}
</div>
The thing is that the key of each object is the identifier in firebase and the value is the object with the data you need, but that object doesn't have a key property, therefore it was evaluated to null.

Related

React project script linter error Missing "key" prop for element in iterator

I am calling from parent component
import { ChildClass } from '../../path/to/child'
<ChildClass
appData={appData}
deviceOS={deviceOS}
mobileFlag={mobileFlag}
/>
and the child component
const ChildClass = props => {
const appData = props.appData,
mobileFlag = props.mobileFlag,
deviceOS = props.deviceOS
return(<div>Hello etc.</div>)}
export { ChildClass }
But I am getting error in linter -
error Missing "key" prop for element in iterator react/jsx-key
Please suggest any solution.
Whenever you display a list of elements in an iteration, you should add a key prop with unique identify values that helps React keep track of changes. Probably your linter is configure to fail if you miss that.
React lists and keys
Looks like you're rendering your ChildClass component inside an iteration. For that, React uses a key component to identify each instance of ChildClass and have control over renders/re-renders of each component.
<ChildClass
key={uniqueId}
appData={appData}
deviceOS={deviceOS}
mobileFlag={mobileFlag}
/>
Key has to be unique, so usually you use an unique property available from your Parent class (could be an event id, username, etc). You can also use index property from an interator function, or libraries like uuidv4.
const childItems = childs.map((child, index) =>
<ChildClass
key={index}
appData={appData}
deviceOS={deviceOS}
mobileFlag={mobileFlag}
/>
)
Although, consider this note from the React documentation:
We don’t recommend using indexes for keys if the order of items may change. This can negatively impact performance and may cause issues with component state
Related article

ReactJS: How to render a collection of objects

So I'm quite new on web development last couple of days. I come from c++ background and I can't wrap my head through all the principles of reactjs. I have 2 classes. The child class called JobAd should render some information that it got from props.
export default class JobAd extends Component {
constructor(props) {
super(props);
this.state ={
index: props.index,
id: props.jobId,
name: props.name,
description: props.description,
location: props.location,
adress: props.adress,
alreadyApplied: props.alreadyApplied,
open: false,
// toggleJob: props.toggleJob,
};
this.toggleJob = props.toggleJob;
}
render() {
return (
<div className={`${styles.jobAd} d-flex` + "job " + (this.state.open ? 'open': '')} key={this.state.index} onClick={() => this.toggleJob(this.state.index)}>
<div className={`${styles.jobTitle}`}>
{this.state.location} - {this.state.name}
</div>
<div className={`${styles.jobDetails}`}>
<div className={`${styles.jobDescription}`}> {this.state.description}</div>
<div className={`${styles.jobAdress}`}>{this.state.adress}</div>
<ApplyButton jobId= {this.props.id} alreadyApplied = {this.props.alreadyApplied}/>
</div>
</div>
)
}
}
The second class, queries a mongoDB db and creates jobAd objects populating them from the info gotten from db.
class JobExplorer extends React.Component
{
...
result.data.jobs.forEach(job => {
var find = job.employees.find(obj => obj === userId);
if (!(find === undefined)) {
alreadyApplied = true;
}
var toPush = new JobAd ({
index: i,
id:job._id,
description:job.description,
name:job.name,
location:job.locationName,
adress:job.locationAdress,
alreadyApplied:alreadyApplied,
open:false,
toggleJob: this.toggleJob.bind(this)
});
jobList2.push(toPush);
console.log("look");
console.log(jobList2)
});
this.setState({
jobList: jobList2
})
this.setState({
error: null,
jobs: result.data.jobs
});
...
render()
{
console.log("look2");
console.log(this.state.jobList);
return (
<div><Navigation />
{this.state.jobList}
</div>
);
}
But I am faced with the following error which I cannot find a fix for.
Error: Objects are not valid as a React child (found: object with keys {props, context, refs, updater, state, toggleJob}). If you meant to render a collection of children, use an array instead.
How should I instantiate those objects so I could render them using the "architecture" I wrote. Is there a fundamental flaw that I have in my classes?
The below snippet doesn't work because new will return an object (this) not the react component.
So, instead of
var toPush = new JobAd({
index: i,
id: job._id,
...
});
jobList2.push(toPush);
you can do this
var toPush = <JobAd
index={i}
id={job._id}
...
/>;
The above snippet works because <JobAd ... /> is converted to React.createElement(JobAd, ... ). However, you still shouldn't do it like this. since there are a lot of better ways to do this. one of them is:
save just the data in joblist and then render the data list on JobAd component
like below:-
render(){
return this.state.joblist.map((job, i) => (
<JobAd
key={job._id}
index={i}
...
/>
));
}
The key is a really important thing. Read about it: https://reactjs.org/docs/lists-and-keys.html
Things that could be improved:-
Don't copy props in the state as you are doing in JobAd class instead directly render the props.
Don't call setState twice as in JobExplorer. you could set all the keys in
setState at the same time. since that would render the component twice.
Suggestions:-
You should avoid using var as that might cause some issues here.
since, you are just a starter, try using functional component first. they are
quite easier to grasp
You seem to have a misconception about state/props in React and web development. It's very normal; I learned python and Java first and many tutorials seem to assume that people just know this already.
"State" in generally refers to variables containing/referring to values that can change without a page refresh in your application. If you know a value is not going to change, it does not need to be held in state. Storing it in a normal variable is exactly what you should do.
"Props" is just another word for arguments that are passed to React components. There's more to it in reality, but as a beginner, that's all you need to really know for now.
So in your job add, things like name, address, jobs, description shouldn't go in state because they aren't going to change as a result of user interaction or for any other reason, unless the underlying data they are loaded from changes, but then that wouldn't be handled by React but instead by the API that your app gets data from. They should just be rendered, so refer to them like this.props.address in your render method. The value for open, however, need to be in state, because that definitely can change.
As for the error, it looks like you are not calling JobAd correctly. You need to use the syntax <Job Ad/> rather than new JobAd...that won't work in React.
I would recommend doing a tutorial to get the basics down.

React can not access variable from return

I am very new to React and I can not solve this issue for a very long time. The idea is the following, I get some user info from database and pass it into cookies. From react side I get this cookies as JSON object and I need to render menu for user depending on values in this object. Right now I pass this object that I get from cookies from parent Component and I try to access it from Settings component. The issue is that I can see access this object from test function, however when I try to access this data from return it gives me undefined error. The function that returns value from cookies is sync, I have no idea why that might happen, please help.....
Since this.state.shopSettings["new_orders"] is boolean, use ternary.
Don't copy props to the state inside constructor as the constructor is executed only once. So updates to the props won't be reflected in the component.
Like this
<button onClick={this.test}>
{props.shopSettings && (props.shopSettings["new_orders"] ? 'true button' : 'false button')}
</button>
It solves very easily with this code, now I can access any key from shopSettings inside return without any issues
class Index extends React.Component {
state = {
shopSettings: Cookies.getJSON('shopSettings')
}
render() {
const {shopSettings} = this.state
if (!shopSettings) {
return null
}
return (
<div>Hello</div>
)
}

setState not .slicing() into state's array the way I'm intending

Background:
I'm developing an itinerary builder which is made up of rows, or component instances named EventContainers, that represent an activity on a user's given day.
The class, shown below, has an array in state.events that accepts EventContainers from the setState in the pushNewEventContainerToState function.
Of note, each EventContainer contains a button that is intended to give the user the ability to onClick an additional row/EventContainer by calling pushNewEventContainerToState.
The same button is also listed as its own component instance, named NewEventButton, and is displayed before any EventContainers.
Any EventContainer that is setStated to state.events is supposed to be placed in the index immediately after the EventContainer that calls setState, not at the beginning or end.
Method
I'm using .slice() in setState with the intention of doing just that => placing the newest EventContainer in the index immediately following the EventContainer that called setstate.
Problem
However, there are three issues I see:
a) Only the very first button, NewEventButton, will actually call setState. The buttons on the new EventContainer's won't do anything.
b) The EventContainers that pass through setState seem to be .pushed() to state.events, not .sliced(), but I don't need them at the end of the array.
c) When I check on Chrome devtools, I see that any EventContainer that's setStated to state.events is undefined.
What I've tried
I've tried placing two different kinds of props directly into the EventContainer that's inside pushNewEventContainerToState:
1. The first prop I tried didn't do anything -> onClick={() => this.pushNewEventContainerToState(index)
2. The second prop I tried was the same as the first except, instead of onClick, I named it pushNewEventContainerToState. This got the button on new EventContainers working but the EventContainers seemed to again be .pushed() instead of .sliced the way I need them to be. Chrome devtools will then show this is defined but I don't see a bound like I do for the first NewEventButton.
Thank you very much for taking a look.
class DayContainer extends React.Component {
constructor(props){
super(props);
this.state = {
events: [],
};
this.pushNewEventContainerToState = this.pushNewEventContainerToState.bind(this);
}
pushNewEventContainerToState(index) {
let newEvent = < EventContainer / > ;
this.setState(prevState => {
const updatedEvents = [...prevState.events.slice(0, index), newEvent, ...prevState.events.slice(index + 1)];
return {
events: updatedEvents
};
})
}
render(){
return (
<>
<div>
<ul>
{
this.state.events === null
? <EventContainer pushNewEventContainerToState={this.pushNewEventContainerToState} />
: <NewEventButton pushNewEventContainerToState={this.pushNewEventContainerToState} />
}
{this.state.events.map((item, index) => (
<li
key={item}
onClick={() => this.pushNewEventContainerToState(index)}
>{item}</li>
))}
</ul>
</div>
</>
)
}
}
Try fixing this part of your function first, looks like it doesn't behave as expected:
[...prevState.events.slice(0, index), newEvent, ...prevState.events.slice(index + 1)]
change to:
[...prevState.events.slice(0, index), newEvent, ...prevState.events.slice(index)]
.slice()'s second argument tells it to stop there, but doesn't include the index of the element. So if you have [1, 2, 3].slice(0,1) in example, you'll get only the first element -> [1].
Hope that fixes the issue.

Although React doesn't allow objects as children, my code clearly says otherwise

So I came across this bizarre behavior — when I try to setState using objects, it only fails in SOME cases.
For example this works,
getUserClaimAmount().then(
userClaimAmount => {
this.setState({ userClaimAmount: userClaimAmount.toString()})
}
);
But the following does not. It will throw an error saying that React children are not allowed to be objects.
getUserClaimAmount().then(
userClaimAmount => {
this.setState({ userClaimAmount: userClaimAmount})
}
);
However, the following works for some reason. "bettingPoolTotal" is the same type as "userClaimAmount" above.
getBettingPoolTotal().then(
bettingPoolTotal => {
this.setState({ total: bettingPoolTotal });
}
);
Below is a screenshot of what the state looks like. It's clear that there's obviously React children that are indeed objects.
Example of React state
You've mixed up the children property and the state of the component.
children property is populated by React when you create the component. Considering the following JSX:
<Parent>
<Child1 />
text here
<Child2 />
</Parent>
The children property of Parent will be an array with three entries: the element created from Child1, string "text here" and the element created from Child2. If you try to pass the plain JavaScript object into the final markup (not into the React component, since it can use it as it want without error, but into native HTML tag), it won't work:
<div>
{{key: value}}
</div>
That's exactly the error you're getting. It seems that there is some element which has this.state.userClaimAmount passed as children entity, and so errors when it gets a plain object instead of string or JSX.
As for the state - it's entirely possible to have plain objects inside it; however, this should be used very cautiously. Consider the following case inside some component which has an obj field in its state:
const obj = this.state.obj;
obj.key = 'value';
this.setState({obj: obj});
The last setState here surprisingly become a no-op, since the state is already changed by this moment - through the reference copied to obj, - and then React, knowing that this is a no-op, doesn't trigger rendering, leaving component the same as before.

Resources