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>
Related
I have a basic component that goes out and gets user info via axios and then sets the users state. But in the component, I have another nested component that is a form type component that sets placeholders, defaultValue, etc.
This is the lifecyle method that gets the data and sets state:
componentDidMount() {
axios.get('https://niftyURLforGettingData')
.then(response => {
console.log(response.data);
const users = response.data;
this.setState({ users });
})
.catch(error => {
console.log(error);
});
}
Nested within this component is my form component:
<FormInputs
ncols={["col-md-5", "col-md-3", "col-md-4"]}
properties={[
{
defaultValue: "I NEED VALUE HERE: this.state.users.id",
}
/>
if I use just:
{this.state.users.id} outside the component it works... but inside form...nothing.
I am quite sure I have to pass the state into this component... but can't quite get it.
I am pretty sure it doesn't work because users is undefined when your component renders for the first time.
Try to initialize that variable in the state doing something like this:
state = {
users: {}
}
and then use a fallback since id will also be undefined doing this:
<FormInputs
ncols={["col-md-5", "col-md-3", "col-md-4"]}
properties={[
{
defaultValue: this.state.users.id || "Fallback Value", // this will render "Fallback value" if users.id is undefined
}
/>
If this is not the case please share more information about your situation.
The title is wordy, however a short / simple example will go a long ways in explaining my question. I have the following start to a component:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchGames } from '../../path-to-action';
class TeamsApp extends Component {
constructor(props) {
super(props);
this.state = {
oldGames: [],
newGames: []
};
}
componentDidMount() {
this.props.dispatch(fetchGames('1617'));
this.setState({ oldGames: this.props.teamGameData });
this.props.dispatch(fetchGames('1718'));
this.setState({ newGames: this.props.teamGameData });
}
...
...
}
function mapStateToProps(reduxState) {
return {
teamGameData: reduxState.GamesReducer.sportsData
};
}
export default connect(mapStateToProps)(TeamsApp);
I would like the action / reducer that corresponds with fetchGames() and gamesReducer to be called twice when the component mounts. This action / reducer grabs some sports data, and I am trying to grab data for two separate seasons (the '1617' season and the '1718' season). The fetchGames() is built correctly to handle the season parameter.
With the current setup, the states aren't being set, and my linter is throwing an error Do not use setState in componentDidMount.
Can I pass a callback to this.props.dispatch that takes the results of the fetchGames() (the teamGameData prop), and sets the oldGames / newGames states equal to this object?
Any help with this is appreciated!
Edit: if i simply remove the this.setState()'s, then my teamGameData prop simply gets overridden with the second this.props.dispatch() call...
Edit 2: I'm not 100% sure at all if having the 2 state variables (oldGames, newGames) is the best approach. I just need to call this.props.dispatch(fetchGames('seasonid')) twice when the component loads, and have the results as two separate objects that the rest of the component can use.
Edit 3: I have the following part of my action:
export const fetchSportsDataSuccess = (sportsData, season) => ({
type: FETCH_NBA_TEAM_GAME_SUCCESS,
payload: { sportsData, season }
});
and the following case in my reducer:
case FETCH_NBA_TEAM_GAME_SUCCESS:
console.log('payload', action.payload);
return {
...state,
loading: false,
sportsData: action.payload.sportsData
};
and the console.log() looks like this now:
payload
{ sportsData: Array(2624), season: "1718" }
but i am not sure how to use the season ID to create a key in the return with this season's data....
Edit 4: found solution to edit 3 - Use a variable as an object key in reducer - thanks all for help on this, should be able to take it from here!
Copying data from the redux store to one's component state is an anti-pattern
Instead, you should modify your redux store, for example using an object to store data, so you'll be able to store datas for multiples seasons :
sportsData: {
'1617': { ... },
'1718': { ... },
}
This way you'll be able to fetch both seasons in the same time :
componentDidMount() {
const seasons = ['1718', '1617'];
const promises = seasons.map(fetchGames);
Promise.all(promises).catch(…);
}
And connect them both :
// you can use props here too
const mapStateToProps = (reduxState, props) => ({
// hardcoded like you did
oldGames: reduxState.GamesReducer.sportsData['1617'],
// or using some props value, why not
newGames: reduxState.GamesReducer.sportsData[props.newSeason],
};
Or connect the store as usual and go for the keys:
const mapStateToProps = (reduxState, props) => ({
games: reduxState.GamesReducer.sportsData,
};
…
render() {
const oldGame = this.props.games[1718];
const newGame = this.props.games[1718];
…
}
Redux is you single source of truth, always find a way to put everything you need in Redux instead of copying data in components
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 having issues finding the best way to organize my data / redux store for the following case:
I want to display a table that shows a list of users. Above the table is a TableHelper component that contains among other features, a 'Filter' form (allows me to filter the list of users based on some search text).
In the code below, I have a Table component that takes a data array as props, and displays one row per element.
The table helper component takes the datasource (all the users), filters it based on the search text, and stores the resulting filtered list in the store, as store.tableHelper.data
The table, on the other hand, renders the filtered data if it exists, and if no filter was applied, just renders the entire data.
This approach has many flaws, as the table helper component can be used with multiple tables across applications, or possibly on the same page. In fact, it would probably be preferable to keep the filtered list out of the store entirely. The only obstacle preventing me from doing that is, the filtered list is computed inside the TableHelper component, and must somehow get returned to the parent container (whose code is below) that feeds the filtered list to the Table.
Any thoughts if it is possible to bypass the use of the store in this case? Or better yet, what would be a better approach to design a solution for this problem, given the following constraints:
keep the TableHelper as its own component, for better reusability
avoid polluting the store with a filtered list for each table
Code:
#connect((store) => {
return {
users: store.user.users,
filteredData: store.tableHelper.data
}
})
export default class Users extends React.Component {
componentDidMount() {
this.props.dispatch(user.getAllUsers());
}
render() {
const sourceData = _.map(this.props.users, function(user, i) {
return {
'name': user.fullName,
'email' : user.email,
'key': i
}
})
// tableData shows the filtered data if such data is defined
// otherwise, tableData is the source data
let tableData = this.props.filteredData;
if (!this.props.filteredData) {
tableData = sourceData;
}
return (
<div>
<TableHelper data={sourceData}/>
<Table tableData={tableData} />
</div>
)
}
}
Thanks a lot
You could pass a callback to the child as a prop, then when your TableHelper component have the filtered data ready it could just pass the info to that callback. You will probably want to have it as a component state inside your parent component so it updates automatically when you pass the filtered data, something like:
#connect((store) => {
return {
users: store.user.users
}
})
export default class Users extends React.Component {
componentDidMount() {
this.props.dispatch(user.getAllUsers());
}
// Callback that will be passed to the TableHelper component.
handleFiltered = (data) => {
this.setState({
filteredData: data
});
}
render() {
const sourceData = _.map(this.props.users, function(user, i) {
return {
'name': user.fullName,
'email' : user.email,
'key': i
}
})
// tableData shows the filtered data if such data is defined
// otherwise, tableData is the source data
tableData = this.state.filtered ? this.state.filtered : sourceData;
return (
<div>
<TableHelper data={sourceData} updateFiltered={this.handleFiltered} />
<Table tableData={tableData} />
</div>
)
}
}
That way, instead of updating the Redux store in your TableHelper component, you would use the callback to pass the data back to it's parent.
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>;
})}