I'm new to React and been struggling a lot at rendering a list of users for a simple user management table I'm trying to build. If anyone is able to see where I went wrong with this it would be greatly appreciated if you could let me know as I've been spending a ton of time on it and reading everything I could find but still have had no success.
I've tried many different variations but this is what I currently have:
import React from 'react';
import { Meteor } from 'meteor/meteor';
import { withTracker } from 'meteor/react-meteor-data';
import PropTypes from 'prop-types';
class ManageUsers extends React.Component {
constructor(props) {
super(props);
this.state = {
users: []
};
}
renderUsers() {
return this.props.users.map((users) => {
return <ManageUsers key={users._id} user={users}/>;
});
};
render() {
return (
<div>
<li>{this.renderUsers()}</li>
</div>
);
}
};
ManageUsers.propTypes = {
users: PropTypes.array.isRequired
};
export default withTracker(() => {
Meteor.subscribe("users");
return {
users: Meteor.users.find({}).fetch()
}
})(ManageUsers);
I receive a few different errors with this in my browser console including:
Uncaught TypeError: Cannot read property 'map' of undefined
Warning: Failed prop type: The prop users is marked as required in
ManageUsers, but its value is undefined.
Uncaught TypeError: Cannot read property 'map' of undefined
Either test that users is defined before mapping, or give it a default prop value.
renderUsers = () => {
const { users } = this.props;
return users && users.map(user => <ManageUsers key={user._id} user={user}/>);
};
or
ManageUsers.defaultProps = {
users: [],
};
Warning: Failed prop type: The prop users is marked as required in ManageUsers, but its value is undefined.
This usually means that although it's a required prop you probably aren't passing it defined data from the parent component.
In this case, it appears the Meteor HOC isn't providing the users data when the react component is mounting. I'm not familiar with Meteor, but perhaps you could make the subscribe function async, and await for it to resolve with a value for the users prop.
export default withTracker(async () => {
Meteor.subscribe("users");
return {
users: await Meteor.users.find({}).fetch()
}
})(ManageUsers);
Related
I am working with some legacy code moved into my new react product. Essentially I have a large grouping of widgets (a lot!) so my employer has asked me NOT to rewrite them but work with them.
I have a BaseWidget:
import { Select } from 'layout/containers'
export default class BaseWidget extends React.Component {
onChange = e => {
this.props.onChange({ target: { value: e } });
};
get operators() { return .... }
get value() { return .... }
get options() {
const { options } = this;
return options;
}
render() {
const multi = this.operators.includes('in');
return (
<div>
<label>{this.props.label}</label>
<Select
value={this.value}
onChange={this.onChange}
options={this.options}
/>
</div>
);
}
}
And I need an AutocompleteWidget which extends from BaseWidget:
import { getAutocompleteOptions } from 'options/modules/autocomplete';
class AutocompleteWidget extends BaseWidget {
onChange = e => {
this.props.onChange({ target: { value: e } });
};
get options() {
return ['options0', 'options1', 'options2'];
}
}
const mapStateToProps = ({ autocomplete }) => {
return {
autocomplete,
};
};
const mapDispatchToProps = dispatch =>
bindActionCreators(
{
getAutocompleteOptions,
},
dispatch,
);
export default connect(mapStateToProps, mapDispatchToProps)(AutocompleteWidget);
I want to use AutocompleteWidget in a specific widget class let's say AssetWidget:
export default class AssetWidget extends AutocompleteWidget {
get options() {
return ['asset0', 'asset1', 'asset2'];
}
}
But my issue is I get this thrown:
TypeError: Super expression must either be null or a function
And the stack trace seems to point to when I try to extend AssetWidget from AutocompleteWidget.
My guess is: modern React-Redux does not play well with class-based inheritance in React components or something with inheriting components that are connected to the redux store/state. Or I am forgetting something technical or conceptual when I transferred my company's old React widget code to this new React codebase.
Looking at other posts: I don't seem to have any circular dependency that I see, and my React version is up-to-date.
I hate class-based React and I usually do everything in functions so I could be forgetting something critical.
Below code returns an error for me
Uncaught (in promise) TypeError: Cannot read property 'setState' of undefined
I am new to react and this seems very basic. Any suggestion what could I be doing wrong. From the json result I want to store all names in my array.
import React, { Component } from "react";
class Search extends Component {
constructor(props) {
super(props);
this.state = {
list: [],
}
}
Search() {
fetch("http://url/getSearchResults")
.then(res => res.json())
.then(
(res) => {
this.setState({
list: res.data.name
})
})
}
This is a very common problem with classes in React - there are two ways to solve this problem:
Bind your methods in the constructor:
constructor(props) {
super(props);
this.state = {
list: [],
}
this.Search = this.Search.bind(this);
}
Use an arrow function instead:
search = () => { ... }
See this post for more information.
Note: using componentDidMount() will be useful if you are trying to make the fetch call on mount - the answer above addresses the issue of this being undefined as per the error you are seeing.
Add componentDidMount() to Search(), it is invoked immediately after a component is mounted (inserted into the tree). Initialization that requires DOM nodes should go here.Its a good place to load data from a remote endpoint.
I have a react component. It recieves questions - array of objects via reducer and getQuestions action.
There is also currentQuestionNumber - an integer, zero by default.
I have questions array in my state. I've mapped state to props.
In my render method I pull questions and currentQuestionNumber from props.
Inside return of my rendermethod I can do {console.log(questions[currentQuestionNumber])} - it logs the first object (index 0) in the array as expected.
A question object looks like this
{id: 1, text: "question 1", options: Array(4), correct: "1"}
But for some reason I can't do {console.log(questions[currentQuestionNumber].text)}- it returns an error TypeError: Cannot read property 'text' of undefined.
How can I fix it and why is this happening ?
import React, { Component } from "react";
import { connect } from "react-redux";
import PropTypes from "prop-types";
import { getQuestions } from "../actions/questionActions";
class Question extends Component {
componentDidMount() {
this.props.getQuestions();
}
render() {
let { questions, currentQuestionNumber } = this.props;
return (
<div>
{console.log(questions[currentQuestionNumber])}
{console.log(questions[currentQuestionNumber].text)}
</div>
);
}
}
Question.propTypes = {
questions: PropTypes.array.isRequired,
getQuestions: PropTypes.func.isRequired
};
const mapStateToProps = state => ({
questions: state.questions.questions,
currentQuestionNumber: state.questions.currentQuestionNumber
});
export default connect(
mapStateToProps,
{ getQuestions }
)(Question);
The error happens because your getQuestions() function is asynchronous and in the first render there is not any questions[currentQuestionNumber]. Then its text is undefined here. So, why can you console.log questions[currentQuestionNumber]?
Probably your questions state is an empty array: []. In the first render if you look carefully it should log undefined without any error since there is an empty array. After getting questions your component is being re-rendered and you see the question.
But, this is different for your .text case. There is not any question at that time, hence when you want to reach a non-existence question's text, react fires this error.
For this kind of situations you should use conditional rendering.
render() {
let { questions, currentQuestionNumber } = this.props;
return (
<div>
{questions.length && console.log(questions[currentQuestionNumber].text)}
</div>
);
}
It's my understanding that the most common use care for iterating over a list of data is map, which is an array method that iterates over an array, but when I tried to apply it here:
import React, { Component } from 'react';
import { View, Text } from 'react-native';
import axios from 'axios';
class QuestionList extends Component {
state = { questions: [] };
componentWillMount() {
axios
.get('https://opentdb.com/api.php?amount=10&difficulty=hard&type=boolean')
.then(response => this.setState({ questions: response.data }));
}
// renderQuestions() {
// return this.state.questions.map(question => <Text>{}</Text>);
// }
render() {
console.log(this.state);
return (
<View>
<Text>{}</Text>
</View>
);
}
}
export default QuestionList;
I ended up getting an error in the Simulator saying that this.state.questions.map() is not a function. I have searched for similar errors online, but they do not apply to my use case.
Keep in mind I commented out the code and erased what I had inside of <Text> because my machine was about to take off.
I don't know what this error means short of not being able to use the map() array helper method, does that mean I need to be applying a different helper method to iterate through this list of questions?
I did a console log of the response object like so:
import React, { Component } from 'react';
import { View, Text } from 'react-native';
import axios from 'axios';
class QuestionList extends Component {
state = { questions: [] };
componentWillMount() {
axios
.get('https://opentdb.com/api.php?amount=10&difficulty=hard&type=boolean')
.then(response => console.log(response));
}
render() {
console.log(this.state);
return (
<View>
<Text>{}</Text>
</View>
);
}
}
export default QuestionList;
and I got back the response object in the console:
from axios with a status of 200 which means the request was successful. You will notice I also go the data property and inside that is the results property and then the category with questions is inside of it:
So I am wondering if its that results property that I need to also implmement, but when I tried it I would get map() undefined.
Your API returns an object, which has no map method.
response.data.results is an array so change it to that if you intend to map over it:
this.setState({ questions: response.data.results }))
It's advisable to use componentDidMount instead of componentWillMount for async update.
I'm using Meteor, React, react-meteor-data, and React Router. I am linking to a page and adding an /:id, which I am then trying to use to query the database and build out the component with the returned data.
The problem is the initial query returns an empty array. I can then see in my render method and componentWillReceiveProps that it returns the data I expect a moment later. The problem is that my component does not re-render. I am using withTracker because I want the component to update and re-render every time the database changes in the targeted Collection.
Here is the React code on the client-side:
export default withTracker((props) => {
const handle = Meteor.subscribe('game', props.match.params.id);
return {
listLoading: !handle.ready(),
game: ActiveGames.find({ _id: props.match.params.id}).fetch(),
};
})(Game);
And here is the publication in 'imports/api/activeGames.js':
import { Meteor } from 'meteor/meteor';
import { Mongo } from 'meteor/mongo';
import { check } from 'meteor/check';
export const ActiveGames = new Mongo.Collection('activeGames');
if (Meteor.isServer) {
Meteor.publish('game', function (id) {
check(id, String);
return ActiveGames.find({ _id: id });
});
Meteor.publish('activeGames', function activeGamesPublication() {
return ActiveGames.find();
});
}
Here is a screenshot of the output I'm getting, with console logs to track the pertinent life cycle methods.
I believe it's a simple matter of doing something with the listLoading prop.
For instance, your Game component could be:
class Game extends Component {
constructor(props){
super(props);
}
renderGames = () => {
return this.props.games.map(g => {
return (
<li>{g.title}</li>
)
})
}
render() {
return (
<ul>
{this.props.listLoading ? <span>Loading...</span> : this.renderGames()}
</ul>
);
}
}
export default withTracker((props) => {
const handle = Meteor.subscribe('game', props.match.params.id);
return {
listLoading: !handle.ready(),
game: ActiveGames.find({ _id: props.match.params.id}).fetch(),
};
})(Game);
This is how I handle the time between subscribing and receiving data.
Hope it helps, though I'm sure you found a solution by now ;)