I developed my first React component that was Function based and I'm now trying to refactor it to be Class based. However, I can't seem to get it to work when trying to convert it over. I'm pretty sure that the issue is with the RenderItem method, and when I try to bind it, I get this error: TypeError: Cannot read property 'bind' of undefined. How can I bind a method that's a child of a parent method? Is this possible, and if not what would be a better solution?
Error when compiling:
Line 35:10: 'state' is assigned a value but never used no-unused-vars
Line 51:9: 'renderItem' is assigned a value but never used no-unused-vars
import React, { useEffect, useReducer } from 'react';
import API from '#aws-amplify/api';
import { List } from 'antd';
import { listQuestions as ListQuestions } from '../../graphql/queries';
export default class QuestionLoader extends React.Component {
state = {
questions: [],
loading: true,
error: false,
form: { asked: '', owner: '' },
};
constructor() {
super();
this.GetQuestion = this.GetQuestion.bind(this);
this.reducer = this.reducer.bind(this);
// this.renderItem = this.renderItem.bind(this);
console.log('constructor', this);
}
reducer(state, action) {
switch (action.type) {
case 'SET_QUESTIONS':
return { ...state, questions: action.questions, loading: false };
case 'ERROR':
return { ...state, loading: false, error: true };
default:
return state;
}
}
GetQuestion() {
const [state, dispatch] = useReducer(this.reducer, this.state);
useEffect(() => {
fetchQuestions();
}, []);
async function fetchQuestions() {
try {
const questionData = await API.graphql({ query: ListQuestions });
dispatch({ type: 'SET_QUESTIONS', questions: questionData.data.listQuestions.items });
} catch (err) {
console.log('error: ', err);
dispatch({ type: 'ERROR' });
}
}
const renderItem = (item) => {
console.log(this);
return <List.Item.Meta title={item.asked} description={item.owner} />;
};
}
render() {
return (
<div>
<List loading={this.state.loading} dataSource={this.state.questions} renderItem={this.renderItem} />
</div>
);
}
}
// export default QuestionLoader;
you cannot mix functional component and class component thing.
useEffect, useReducer are wrong one to use with class component.
Don't use bind, use arrow function to create the method. Remove constructor.
import React from 'react';
export default class QuestionLoader extends React.Component {
state = {
data: "name"
};
handleClick = () => {
this.setState({
name:"test"
})
}
render() {
return (<div> {this.state.name}
<button onClick={this.handleClick}></button>
</div>
}
}
Related
I am using React redux with firebase realtime database.
In App.js I am dispatching an action fetchAllPosts
App.js
class App extends Component {
componentDidMount() {
this.props.fetchAllPosts();
}
render() {
return (
<div className="App">
// something ...
</div>
);
}
}
const mapDispatchToProps = dispatch => {
return {
fetchAllPosts: () => {dispatch(allPosts())}
}
}
My action looks like this (I am using redux-thunk):
action
export function allPosts() {
return (dispatch) => {
firebase.database().ref('posts/').on('value', (snapshot) => {
dispatch({type: "ALL_POSTS", postsArray: snapshot.val(), loading: false})
})
}
}
Then I am combining reducers (I know in this case it is not necessary):
const rootReducer = combineReducers({
allPosts: postsReducer
})
My reducer looks like this:
reducer
const initialState = {
allPosts: []
}
const postsReducer = (state = initialState, action) => {
switch(action.type) {
case "ALL_POSTS" :
console.log("action payload all posts", action.postsArray)
return {
...state,
loading: false,
allPosts: action.postsArray
}
break;
default:
return state
}
return state
}
And finally: my SinglePostview component looks like this:
SinglePostview.js
import React, {Component} from 'react';
import {connect} from 'react-redux';
class SinglePostview extends Component {
render() {
console.log("ppp", this.props)
return (
<h2>{this.props.post.title}</h2>
)
}
}
const mapStateToProps = (state, ownprops) => {
const postId = ownprops.match.params.postid
return {
post: state.allPosts.allPosts[postId]
}
}
export default connect(mapStateToProps)(SinglePostview);
Here when the render method is executing, this.props.post is undefined and I have the error:
TypeError: Cannot read property 'title' of undefined.
The problem is: when the app loads for the first time, props.post is undefined (so I have an error) and after about 1 second it receives the value but it doesn't change anything - the error still exists and the value is not displaying.
Could anyone help me?
Assuming your reducer is fine, you can fix this by
changing this
render() {
return (
<h2>{this.props.post.title}</h2>
)
}
To this:
render() {
if (!this.props.post){
return null;
}
return (
<h2>{this.props.post.title}</h2>
)
}
or
render() {
return (
<h2>{this.props.post && this.props.post.title}</h2>
)
}
You are defining allPosts to be an array
const initialState = {
allPosts: []
}
But you are trying to access it like an object.
state.allPosts.allPosts[postId]
Hence, if your state.allPosts.allPosts is an array , try using the ES6 find() method to get a post from the array with the postId.
Assuming
state.allPosts.allPosts = [
{postId: 1,title:'abcd'},
{postId:2,title:'def'}
]
state.allPosts.allPosts.find(post => postId === post.postId)
After get the comments array from post component and pass it to comments component
the logs start to show the error in the screenshot below
the components are:
import React, { Component } from "react";
import axios from "axios";
import Comments from "../components/comments";
class Article extends Component {
constructor(props) {
super(props);
this.state = {
title: "",
error: "",
comment: ""
};
}
componentDidMount() {
this.getComments();
}
getComments = () => {
const {
match: { params }
} = this.props;
return axios
.get(`/articles/${params.id}/comments`, {
headers: {
Accept: "application/json",
"Content-Type": "application/json",
}
})
.then(response => {
return response.json();
})
.then(response => this.setState({ comments: response.comments }))
.catch(error =>
this.setState({
error
})
);
};
render() {
return (
<div>
{this.state.title}
<div>
<h2>Comments</h2>
<Comments
getComments={this.getComments}
/>
</div>
</div>
);
}
}
export default Article;
and Comments component
import React, { Component } from "react";
import PropTypes from "prop-types";
import Comment from "./comment";
import axios from "axios";
import Article from "../screens/article";
class Comments extends Component {
constructor(props) {
super(props);
this.state = {
comments: [],
comment: "",
error: ""
};
this.load = this.load.bind(this);
this.comment = this.comment.bind(this);
}
componentDidMount() {
this.load();
}
load() {
return this.props.getComments().then(comments => {
this.setState({ comments });
return comments;
});
}
comment() {
return this.props.submitComment().then(comment => {
this.setState({ comment }).then(this.load);
});
}
render() {
const { comments } = this.state;
return (
<div>
{comments.map(comment => (
<Comment key={comment.id} commment={comment} />
))}
</div>
);
}
}
export default Comments;
so, I've tried to pass it by props, and set the state on comments component.
and instead of use just comments.map I've tried to use this.state but show the same error in the logs.
So, someone please would like to clarify this kind of issue?
seems pretty usual issue when working with react.
If an error occurs you do:
.catch(error => this.setState({ error }) );
which makes the chained promise resolve to undefined and that is used as comments in the Comments state. So you have to return an array from the catch:
.catch(error => {
this.setState({ error });
return [];
});
Additionally it woupd make sense to not render the Comments child at all if the parents state contains an error.
The other way is checking whether it’s an array and if so check it’s length and then do .map. You have initialized comments to empty array so we don’t need to check whether it’s an array but to be on safer side if api response receives an object then it will set object to comments so in that case comments.length won’t work so it’s good to check whether it’s an array or not.
Below change would work
<div>
{Array.isArray(comments) && comments.length>0 && comments.map(comment => (
<Comment key={comment.id} commment={comment} />
))}
</div>
The first time the comments component renders there was no response yet so comments were undefined.
import React, { Component } from "react";
import PropTypes from "prop-types";
import Comment from "./comment";
import axios from "axios";
import Article from "../screens/article";
class Comments extends Component {
constructor(props) {
super(props);
this.state = {
comments: [],
comment: "",
error: ""
};
this.load = this.load.bind(this);
this.comment = this.comment.bind(this);
}
componentDidMount() {
this.load();
}
load() {
return this.props.getComments().then(comments => {
this.setState({ comments });
return comments;
});
}
comment() {
return this.props.submitComment().then(comment => {
this.setState({ comment }).then(this.load);
});
}
render() {
const { comments } = this.state;
if (!comments) return <p>No comments Available</p>;
return (
<div>
{comments.map(comment => (
<Comment key={comment.id} commment={comment} />
))}
</div>
);
}
}
export default Comments;
I have a component parent and a component child with some props connected to the parent state.
In the parent I call setState but the componentWillReceiveProps function of the child is not fired.
More precisaly, its fired in a certain point of the parent, its not fired in another point.
This is the parent:
... imports
class HomeScreen extends Component {
constructor(props) {
super(props);
dispatchFbPermissionAction = this.dispatchFbPermissionAction.bind(this);
this.state = {
fbPermissions: [],
}
}
componentDidMount () {
this._loadInitialState();
}
_responsePermissionsCallback(error: ?Object, result: ?Object) {
if (error) {
log('Error fetching data: ' + error.toString());
} else {
dispatchFbPermissionAction(result.data);
}
}
dispatchFbPermissionAction = (data) => {
// **NOT FIRED**
this.setState({
fbPermissions: data
});
this.props.fbPermissionsLoaded(data);
}
async _loadInitialState() {
AccessToken.getCurrentAccessToken().then(
(data) => {
if (data) {
const infoRequest = new GraphRequest(
'/me/permissions',
null,
this._responsePermissionsCallback,
);
new GraphRequestManager().addRequest(infoRequest).start();
// **FIRED**
this.setState({
...
});
this.props.loggedIn();
}
}
);
}
render () {
const { navigation } = this.props;
return (
<Container>
<ScrollableTabView
<View tabLabel="ios-film" style={styles.tabView}>
<Text style={styles.tabTitle}>{_.toUpper(strings("main.theatres"))}</Text>
<ListTheatre navigation={this.props.navigation} filterText={this.state.filterText} isLoggedIn={this.state.isLoggedIn} fbPermissions={this.state.fbPermissions}></ListTheatre>
</View>
</ScrollableTabView>
</Container>
)
}
}
const mapStateToProps = (state) => {
return {
isLoggedIn: state.isLoggedIn,
listTheatre: state.listTheatre,
listMusic: state.listMusic
};
};
// wraps dispatch to create nicer functions to call within our component
const mapDispatchToProps = (dispatch) => ({
startup: () => dispatch(StartupActions.startup()),
loggedIn: () => dispatch({
type: LOGGED_IN
}),
fbPermissionsLoaded: (data) => dispatch({
type: FB_PERMISSIONS_LOADED,
fbPermissions: data
})
});
export default connect(mapStateToProps, mapDispatchToProps)(HomeScreen)
And this is the child:
... imports
class ListTheatre extends Component {
constructor(props) {
super(props);
this.state = {
...
}
}
componentWillReceiveProps(nextProps) {
log(this.props)
}
shouldComponentUpdate(nextProps, nextState) {
return !nextState.fetching;
}
render() {
const { navigate } = this.props.navigation;
return (
<SectionList
...
/>
)
}
}
ListTheatre.propTypes = {
isLoggedIn: PropTypes.bool.isRequired,
}
const mapStateToProps = (state) => {
return {
isLoggedIn: state.isLoggedIn
};
};
const mapDispatchToProps = (dispatch) => ({
startup: () => dispatch(StartupActions.startup())
});
export default connect(mapStateToProps, mapDispatchToProps)(ListTheatre);
I do not why the setState after the GraphRequestManager().addRequest call works like a charm (the componentWillReceiveProps function of the child is fired), while the setState in the dispatchFbPermissionAction function does not fire the componentWillReceiveProps function of the child.
This is due to connect/Connect(ListTheatre) that wraps your ListTheatre component implemented sCU(shouldComponentUpdate) internally for you, turn it off by setting pure option of connect to false like
export default connect(mapStateToProps, mapDispatchToProps, null, {pure: false})(ListTheatre)
[pure] (Boolean): If true, connect() will avoid re-renders and calls to mapStateToProps, mapDispatchToProps, and mergeProps if the relevant state/props objects remain equal based on their respective equality checks. Assumes that the wrapped component is a “pure” component and does not rely on any input or state other than its props and the selected Redux store’s state. Default value: true
I'm working my way learning some react+redux-thunk and I've put together a simple form that hits an API and retrieves some jokes. My core component and code:
containers/AsyncApp.js
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import {bindActionCreators} from 'redux';
import SearchJokes from '../components/SearchJokes';
import Jokes from '../components/Jokes';
import {fetchJokes} from '../actions';
class AsyncApp extends Component {
constructor(props) {
super(props)
this.handleSubmit = this.handleSubmit.bind(this)
this.handleInput = this.handleInput.bind(this)
this.state = {searchText: ''};
}
handleSubmit(e){
e.preventDefault();
//const {searchText} = this.props;
console.log('button clicked ' + this.state.searchText);
this.props.fetchJokes(this.state.searchText);
}
handleInput = (e) => {
this.setState({
searchText: e.target.value,
})
}
render(){
const { jokes, isFetching } = this.props
return(
<div>
<SearchJokes
handleSubmit={this.handleSubmit}
onChange={this.handleInput}
searchText={this.state.searchText}
/>
{jokes ? (<Jokes jokes={jokes}/>) : (<div></div>)}
</div>
)
}
}
function mapStateToProps(state) {
return {
isFetching: state.isFetching,
jokes: state.items
}
}
function mapDispatchToProps(dispatch) {
return{
fetchJokes: bindActionCreators(fetchJokes, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(AsyncApp)
actions/index.js
export const REQUEST_JOKES = 'REQUEST_JOKES'
export const RECEIVE_JOKES = 'RECEIVE_JOKES'
function requestJokes(term) {
return {
type: REQUEST_JOKES,
term
}
}
function receiveJokes(term, json) {
return {
type: RECEIVE_JOKES,
term,
jokes: json.results.map(joke => joke)
}
}
export function fetchJokes(term) {
return dispatch => {
dispatch(requestJokes(term))
return fetch(`https://icanhazdadjoke.com/search?term=${term}`, {
method: 'GET',
headers: {
'Accept': 'application/json'
}
}).then(response => response.json())
.then(json => dispatch(receiveJokes(term, json)))
}
}
reducers/index.js
import { combineReducers } from 'redux'
import {
REQUEST_JOKES,
RECEIVE_JOKES
} from '../actions'
function jokesBySearch(state = {}, action) {
switch(action.type){
case REQUEST_JOKES:
return Object.assign({}, state, {isFetching: true, items: []})
case RECEIVE_JOKES:
return Object.assign({}, state, {
isFetching: false,
items: action.jokes,
})
default:
return state
}
}
const rootReducer = combineReducers({
jokesBySearch,
})
export default rootReducer
The form component works, and I can see the json array returned as part of the action. But the value of this.props.jokes is empty or undefined in the console.log and I'm wondering how to populate it once the results from the API call are returned.
I see two problems in this code.
First you don't need to use combineReducers when you only have one reducer. Just keep it simple and add complexity only when absolutely necessary. Instead of combineReducers
export default jokesBySearch
If you do use combineReducers be mindful how it affects your state.
Second there is no need to use bindactioncreators here, just dispatch the action (and don't forget to pass the term).
function mapDispatchToProps(dispatch) {
return{
fetchJokes: (term) => dispatch(fetchJokes(term))
}
}
See here when you need to use bindactioncreators (pretty rare use case)
In general good way to lear a library like redux is just use the core functionality until you encounter problem and only then add complexity.
I have a problem with my redux app: it correctly dispatches the relevant action and updates the state, but for some reason the UI doesn't get updated. 99% of questions about this seem to be caused because the state actually gets mutated, but I'm pretty sure that's not the case. Here are the relevant files:
Container component:
import React from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import requestEvents from 'actions/feedActions';
import { FeedElement, feedElementTypes } from '../components/feed/feedElement';
class Feed extends React.Component {
componentWillMount() {
this.props.requestEvents();
}
render() {
console.log('Rendering feed');
const listOfFeedElements = this.props.listOfFeedElements;
let elementsToDisplay;
if (listOfFeedElements.length === 0) {
elementsToDisplay = <li><p> No elements to display</p></li>;
} else {
const numOfElementsToDisplay = listOfFeedElements.length <= 10 ?
listOfFeedElements.length : 10;
const sortedElementsToDisplay = listOfFeedElements.concat().sort(
(e1, e2) => e1.timestamp - e2.timeStamp);
elementsToDisplay =
sortedElementsToDisplay
.slice(0, numOfElementsToDisplay)
.map(el => FeedElement(el));
}
return (
<div className="socialFeedContainer">
<h1> Social feed </h1>
<ol>
{elementsToDisplay}
</ol>
</div>
);
}
}
Feed.propTypes = {
requestEvents: PropTypes.func.isRequired,
listOfFeedElements: PropTypes.arrayOf(PropTypes.shape({
timeStamp: PropTypes.number.isRequired,
type: PropTypes.oneOf(Object.keys(feedElementTypes)).isRequired,
targetDeck: PropTypes.string.isRequired,
concernedUser: PropTypes.string.isRequired,
viewed: PropTypes.bool.isRequired })),
};
Feed.defaultProps = {
listOfFeedElements: [],
};
function mapDispatchToProps(dispatch) {
return bindActionCreators({ requestEvents }, dispatch);
}
function mapStateToProps(state) {
const { feedState } = state.local.feed.listOfFeedElements;
return {
feedState,
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Feed);
Reducers:
import Immutable from 'seamless-immutable';
import { types } from 'actions/feedActions';
const initialState = Immutable({
listOfFeedElements: [],
sentRequestForList: false,
receivedList: false,
});
function feedReducer(state = initialState, action) {
switch (action.type) {
case types.REQUEST_FEED_ELEMENTS:
return Immutable.merge(state, {
sentRequestForList: true,
receivedList: false,
});
case types.RECEIVED_LIST:
return Immutable.merge(state, {
sentRequestForList: false,
receivedList: true,
listOfFeedElements: action.payload.listOfNotifications,
});
default:
return state;
}
}
export default feedReducer;
Saga:
import { takeLatest, put } from 'redux-saga/effects';
import { types } from 'actions/feedActions';
import feedApiCall from './feedApiCall';
export function* getFeedFlow() {
try {
console.log('sent request for feed');
const listOfNotifications = feedApiCall();
yield put({
type: types.RECEIVED_LIST,
payload: {
listOfNotifications,
},
});
} catch (error) {
yield put({
type: types.RECEPTION_ERROR,
payload: {
message: error.message,
statusCode: error.statusCode,
},
});
}
}
function* feedUpdateWatcher() {
yield takeLatest(types.REQUEST_FEED_ELEMENTS, getFeedFlow);
}
export default feedUpdateWatcher;
The actions get dispatched and the state modified (In the end I have a list with the components). However the component renders only once, as I can check from the call to console.log.
The root component looks quite suspiciously.
First, proceeding from a code and agreements on naming, FeedElement the class looks as React, however it is used as simple function in map. Whether there was no similar look in view of something:
elementsToDisplay =
sortedElementsToDisplay
.slice(0, numOfElementsToDisplay)
.map(el => (<FeedElement {...el} />));
Secondly, you return object const {feedState} = state.local.feed.listOfFeedElements; and further you address this.props.listOfFeedElements - such field in principle just doesn't exist. And from where a part of state.local.feed undertakes?
In addition, time you is used by redux and saga, it is better to make generally a pure functional part and to get rid of a call of componentWillMount which not really corresponds to the expected architecture.