I have a user db in Firebase that uses the unique user id as the Key and within that key/value pairs like name: 'jane doe', email: 'jane#doe.com', etc. What I want to do is map the object and get the key values within. I'm able to get the Key (user id), but not the object key value pairs. Here's my code:
export default class Home extends Component {
constructor(props) {
super(props);
var users = {};
this.state = { users };
}
componentDidMount() {
const dbRoot = firebaseDb.database().ref().child('users');
dbRoot.on('value', snap => {
const dbUsers = snap.val();
this.setState({
users: dbUsers
});
});
}
render() {
return (
<div>
<div>
{Object.keys(this.state.users).map(function(user, i) {
return <div key={i}>Key: {user}, Value: {user.name}</div>;
})}
</div>
</div>);
}
}
user.name comes back undefined. I've tried using this.state.users.name but I get a "state is undefined" message. Can someone point me in the right direction. Thanks!
You have two main problems with your code. The first is that you cannot access this.state inside the callback of your map() function because this is bound to that function and not to the whole class. There are a couple of ways to keep the this variable bound to the class but the cleanest way is to use the Arrow function syntax.
Once you do that, you will still have a problem. You are using Object.keys which will only map over the keys of your result, but you are treating it as if it will pass the whole user object to the function. In order to access the user object inside of your callback function, you will need to use the bracket notation with the key.
With those two things in mind, your code should look something like this:
{Object.keys(this.state.users).map(key => {
return <div key={key}>Key: {key}, Value: {this.state.users[key].name}</div>;
})}
Related
Admit it. Being new to JavaScript in 2018 is difficult. Coming from languages like C#, Java and Typescript(yeah subset of js..) where type safety is key, Javascript just keep f***** me over. I struggle with something simple like updating an array..
So I have this React component, where the state is defined like this:
class Form extends React.Component {
constructor(props) {
super(props);
this.state = {
show: false,
shoes: []
};
}
....
...
}
The shoes is an array of undefined(?)
This array is passed to a stateless component which looks like this
const Shoelist = props => {
return (
<Wrapper>
{props.shoes.map((shoe, i) => (
<div key={i}>
<Shoe shoe={shoe} />
<Separator />
</div>
))}
</Wrapper>
);
};
I in my Form component, I have a method which is supposed to react(doh) on onClick methods. In this method I get a parameter with a new shoe to add in this list. This is very it stops for me in javascript - something which is faaaaairly easy in all other languages that we've being using for the past years..
I've tried several ways:
1#
addShoe(shoe) {
this.setState(state => {
const list = state.shoes.push(shoe);
return {
list
};
});
}
This results in an error: Uncaught TypeError: Cannot read property 'push' of undefined Do I need to define shoes as an Array? I thought the [] was enough
2#
I googled, I do that alot. I found one blog post saying something about react-addons-update. I installed this by running yarn add and code looks like this:
addShoe(shoe) {
this.setState(update(this.state, { shoes: { $push: [shoe] } }));
}
which results in Uncaught Error: update(): expected target of $push to be an array; got undefined.
Help! How difficult can this be?
EDIT
I pass this method into another component like this:
<ShoeModal onClose={this.addShoe} />
in the ShoeModal component this is bound to a onClick method:
<FinishModalButton
onClick={this.props.onClose.bind(this, this.state.shoe)}>
....
</FinishModalButton>
ShoeModal.propTypes = {
onClose: PropTypes.func.isRequired
};
You can do it this way:
this.setState({
shoes: [...this.state.shoes, newShoe]
})
... adds all elements from this.state.shoes
With your updates we can see that the issue is the way the addShoe callback is passed. It's being invoked as a function instead of a method of an object, so it loses context.
Change it to:
<ShoeModal onClose={this.addShoe.bind(this)} />
or
<ShoeModal onClose={shoe => this.addShoe(shoe)} />
In addition, .push returns the count of the array, so the following line won't give you what you expect:
const list = state.shoes.push(shoe);
See #merko's answer for a solution.
Firstly, your addShoe method is not an arrow function.
Using arrow functions because the context this is of the component.
Second, you are returning the object {list}. This sets the variable list in state.
Also push to the new list variable instead of mutating state.
Change your function to
addShoe = (shoe) => {
this.setState(state => {
let list = state.shoes;
list.push(shoe);
return {
shoes : list
};
});
}
I have firebase key that I would like to pass from 1 component to the other, although it seems to just show as blank. In my List.js I can see I am able to get the key (through this.props.item.key) and display the key. But when I want pass it to Editblog.js it does not show anything.
List.js
const { blogDescription, key, ownerId } = this.props.item.values;
Actions.edit_blog({ values: this.props.item.values });
I want to pass key to Editblog.js here I find this.state.blogDescription gives me the correct result but when I use this.state.key it shows up as blank.
Editblog.js
this.state = {
blogDescription: props.values.blogDescription,
key: props.values.key,
ownerId: props.values.ownerId
};
<Text>{this.state.key}</Text>
Most props on a JSX element are passed on to the component, however, there are two special props (ref and key) which are used by React, and are thus not forwarded to the component.
In your case you can pass the key as a different prop
Actions.edit_blog({ values: this.props.item.values, id: this.props.item.values.key });
And access it like this
this.state = {
blogDescription: props.values.blogDescription,
key: props.id,
ownerId: props.values.ownerId
};
<Text>{this.state.key}</Text>
I have an object in state ("car") with multiple keys, one of which is an array ("features"). There's a couple things I'm trying to do with it.
I want to push another string (another feature) onto the "features" array every time I click the "Add Feature" button.
I want to be able to update the state of each string/feature in the "features" array when I type in the respective input.
I've researched this online quite a bit and haven't found anything (maybe because this isn't possible). Either way, here's my code:
class Car extends React.Component {
state = {
car: {make: 'Toyota', model: 'Camry', features: []},
}
handleChange = (e, index) => {
const value = e.target.value
let features = this.state.car.features.slice() // create mutable copy of array
features = features[index].concat(value)
this.setState({...this.state.car, features: features})
}
handleAddFeature = () => {
let features = this.state.car.features.slice()
features.push('')
this.setState({...this.state.car, features: features})
}
render() {
return (
{
this.state.car.features.map((f, index) => { return <input key={index} onChange={e => this.handleChange(e, index)}>{feature}</input>
}
<button onClick={this.handleAddFeature}>Add Feature</button>
)
}
}
Okay, I got this working in a very similar way to #Snkendall's answer. My handleChange function is listed below:
handleChange = (e, index,) => {
let array = this.state.car.features.slice() // create mutable copy of the array
array[index] = e.target.value // set the value of the feature at the index in question to e.target.value
const newObj = { ...this.state.car, features: array } // create a new object by spreading in the this.state.car and overriding features with our new array
this.setState({ car: newObj }) // set this.state.car to our new object
}
The difference between this solution and #Snkendall's is the defining of a newObj variable. That turns out to the be the only way to update an individual key nested inside a state object.
There are a few things that could be causing you problems... if your component has a state, you should use a constructor, and bind your 'this' references inside it to prevent 'this' from referencing the global. You just wrap your state like this:
class Car extends React.Component {
constructor() {
super()
this.state = {
car: {make: 'Toyota', model: 'Camry', features: []},
}
this.handleChange = this.handleChange.bind(this)
this.handleAddFeature = this.handleAddFeature.bind(this)
}
This is a really great article for thinking about 'this': http://2ality.com/2017/12/alternate-this.html
Another area that might cause you problems is features = features[index].concat(value)... because you're concatting the input tag's value onto the current string on state over and over again with every change (keystroke). You can just reset the value of the element at that index in the array like this:
handleChange = (e, index) => {
const value = e.target.value
let features = this.state.car.features.slice()
features[index] = value
this.setState({...this.state.car, features})
}
and that way, each keystroke just resets the value on state to reflect the change created in the input. You actually wouldn't need to use the handleAddFeature at all, since the state is already updated with handleChange.
I'm changing features:features to just features because ES6 destructuring has this fun thing where if a key and it's value is the same, you only need to reference it once, and it figures it out. It's just a cool way to keep your code even DRYer.
Ad.1
handleAddFeature = () => {
const car = this.state.car
car.features.push('Your feature')
this.setState({ car })
}
Just create copy and push to car features new value.
Ad. 2
Create component called e.g. Feature. He will have own state where you modify string and through props you can extract data to your "car".
I'm considering using Redux for my app, but there's a common use case that I'm not sure how to handle with it. I have a component that displays some object and allows the user to edit it. Every action will create a shallow copy of the object, but what then? How is the component supposed to know how to update the storage with it? In the samples I see that the component is passed a key instead of the actual object, but doesn't that break the concept of incapsulation, since a component isn't supposed to know where it's state/props come from? I want the component to be fully reusable, so it receives an object and information on how to update it in a more general form, which seems to be awkward to implement with Redux (I'm going to have to pass write callbacks to every component, and then chain them somehow).
Am I using Redux wrong, or is there a more suitable alternative for this use case? I'm thinking of making one myself (where every state object knows it's owner and key via some global WeakMap), but I don't want to be reinventing the wheel.
For instance, if my storage looks like this:
Storage = {
items: {
item1: { ... },
item2: { ... },
...
},
someOtherItems: {
item1: { ... },
...
},
oneMoreItem: { ... },
};
I want to be able to display all item objects with the same component. But the component somehow has to know how to write it's updated item back to the storage, so I can't just pass it item1 as key. I could pass a callback that would replace a specific item in the (cloned) storage, but that doesn't work well if, for instance, I have a component that displays a list of items, since I would have to chain those callbacks somehow.
This is a common use case, and yes - you're missing the point here. react/redux makes this really easy.
I usually structure it as follows: Components receive a modelValue object prop and changeValue function prop. The former is the current value, the latter is the function we call to change the value. These props are going to be supplied by redux.
Now we write a connect hoc (higher order component), a simple example might look like this:
const mapStateToProps = (state, ownProps) => {
return {
modelValue: _.get(state, ownProps.model),
};
}
const mapDispatchToProps = (dispatch, ownProps) => {
return {
changeValue: (val) => dispatch({
type: "your/reducer/action",
model: ownProps.model,
value: val,
})
};
};
const mergeProps = (stateProps, dispatchProps, ownProps) => {
return {
...stateProps,
...dispatchProps,
...ownProps,
};
};
const MyConnectedComponent = connect(mapStateToProps, mapDispatchToProps, mergeProps)(MyGenericComponent);
This is an example where we pass in a model string to the hoc, and it wires up modelValue and changeValue for us. So now all we need to do is pass in a model like "some.javascript.path" to our component and that's where it will get stored in the state. MyGenericComponent still doesn't know or care about where it's stored in the state, only MyConnectedComponent does.
Usage would be as follows:
<MyConnectedComponent model="some.path.in.the.state" />
And inside MyGenericComponent just consume modelValue for the current value, and execute changeValue to change the value.
Note that you need to also wire up a redux reducer to handle your/reducer/action and actually do the update to the state, but that's a whole other topic.
Edit
You mentioned that you need sub components to be aware of the parent state, this can be achieved by passing model via context. The following examples are using recompose:
const mapStateToProps = ...
const mapDispatchToProps = ...
const mergeProps = ...
const resolveParentModel = (Component) => {
return (props) => {
// we have access to 'model' and 'parentModel' here.
// parentModel comes from parent context, model comes from props
const { parentModel, model } = props;
let combinedModel = model;
// if our model starts with a '.' then it should be a model relative to parent.
// else, it should be an absolute model.
if (model.startsWith(".")) {
combinedModel = parentModel + model;
}
return <Component {...props} model={combinedModel} />;
}
}
const myCustomHoc = (Component) => (
// retrieve the current parent model as a prop
getContext({ parentModel: React.PropTypes.string })(
// here we map parent model and own model into a single combined model
resolveParentModel(
// here we map that combined model into 'modelValue' and 'changeValue'
connect(mapStateToProps, mapDispatchToProps, mergeProps)(
// we provide this single combined model to any children as parent model so the cycle continues
withContext({ parentModel: React.PropTypes.string }, (props) => props.model)(
Component
)
)
)
)
);
In summary, we pass a context value parentModel to all children. Each object maps parent model into it's own model string conditionally. Usage would then look like this:
const MyConnectedParentComponent = myCustomHoc(MyGenericParentComponent);
const MyConnectedSubComponent = myCustomHoc(MyGenericSubComponent);
<MyConnectedParentComponent model="some.obj">
{/* the following model will be resolved into "some.obj.name" automatically because it starts with a '.' */}
<MyConnectedSubComponent model=".name" />
</MyConnectedParentComponent>
Note that nesting this way could then go to any depth. You can access absolute or relative state values anywhere in the tree. You can also get clever with your model string, maybe starting with ^ instead of . will navigate backwards: so some.obj.path and ^name becomes some.obj.name instead of some.obj.path.name etc.
Regarding your concerns with arrays, when rendering arrays you almost always want to render all items in the array - so it would be easy enough to write an array component that just renders X elements (where X is the length of the array) and pass .0, .1, .2 etc to each item.
const SomeArray = ({ modelValue, changeValue }) => (
<div>
{modelValue.map((v, i) => <SomeChildEl key={i} model={"." + i} />)}
<span onClick={() => changeValue([...modelValue, {}])} >Add New Item</span>
</div>
);
I am coming from Angular 1.x and looking to update an unordered list with React / Redux.
In console.log, I am seeing the array being updated, but it doesn't seem to bind to the DOM. I have the following --
onKeyPress of an input, I have a function that pushes to messages array.
<ul className="list-inline">
{messages.map(function(message, key){
return (
<li key={key} message={message}>{message}</li>
);
})}
</ul>
Update
I have the following (but no luck yet) Some notes. I am using Firebase to listen for events, and add to an array. Wondering if its a bind issue? --
class Comments extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {messages: this.props.messages};
}
componentDidMount() {
const path = '/comments/all';
// Firebase watches for new comments
firebase
.database()
.ref(path)
.on('child_added', (dataSnapshot) => {
this.state.messages.push(dataSnapshot.val());
this.setState({
messages: this.state.messages
});
//console.log(dataSnapshot.val());
});
}
render() {
const messages = this.state.messages;
return (
<ul className="list-inline">
{messages.map(function(message, key){
<li key={key}>{message}</li>
})}
</ul>
);
}
}
You need messages to be set in the components state.
getInitialState() {
return {
messages: []
}
}
Then in your function, set the state:
this.setState({messages: updatedMessages})
and then map over the messages state, or a messages variable in render:
const messages = this.state.messages;
<ul className="list-inline">
{messages.map(function(message, key){
etc...
put messages array and set state change to render DOM. You should read https://facebook.github.io/react/docs/component-specs.html
Two issues:
You mustn't directly mutate the state object in React (see: Do Not Directly Modify State). Instead, provide a new array via setState with the new entry in it.
When updating state based on existing state, you must use the function callback version of setState, not the version accepting an object, because state updates are asynchronous and may be merged (see: State Updates May Be Asynchronous, though it's really "will," not "may"). Using the object version often happens to work, but it isn't guaranteed to; indeed, it's guaranteed not to, at some point.
Let's look at various ways to update an array:
Adding to the end (appending):
this.setState(({messages}) => ({
messages: [...messages, newValue]
}));
In your case, newValue would be dataSnapshot.val().
(We need the () around the object initializer because otherwise the { would seem to start a full function body instead of a concise expression body.)
Adding to the beginning (prepending):
Largely the same, we just insert the new element in a different place:
this.setState(({messages}) => ({
messages: [newValue, ...messages]
}));
(We need the () around the object initializer because otherwise the { would seem to start a full function body instead of a concise expression body.)
Updating an existing item in the array
Suppose you have an array of objects and want to update one you have in the variable targetElement:
this.setState(({messages}) => {
messages = messages.map(element => {
return element === targetElement
? {...element, newValue: "new value"}
: element;
});
return {messages};
}));
Removing an existing item in the array
Suppose you have an array of objects with id values and want to remove the one with targetId:
this.setState(({messages}) => {
messages = messages.filter(element => element.id !== targetId);
return {messages};
}));
By Index
Warning: Updating arrays in React by index is generally not best practice, because state updates are batched together and indexes can be out of date. Instead, work based on an identifying property or similar.
But if you have to use an index and you know it won't be out of date:
this.setState(({messages}) => {
messages = messages.filter((e, index) => index !== targetindex);
return {messages};
}));