I have a small problem with componentWillMount when I want to update the component's state I get this error.
the code is this :
export default class RouterComponent extends Component {
constructor(props) {
super(props);
this.state = {
isLogin: null
}
}
componentWillMount = async () => {
AsyncStorage.getItem('#MyUserFireBase:key').then(response => {
this.setState({ 'isLogin': true });
}).done();
}
render() {
return (
<Router>
<Stack key="root" hideNavBar>
<Stack key="main">
<Scene key="login" initial={!this.state.isLogin} component={LoginForm} title="Login" />
<Scene key="employeeList" initial={this.state.isLogin} component={EmployeeList} title="Employee List" />
</Stack>
</Stack>
</Router >
)
}
}
I have more than 3 days looking for the solution and I can not find it. What am I doing wrong?
Thanks for your time
Check out component lifecycle methods to help you with your problem: https://engineering.musefind.com/react-lifecycle-methods-how-and-when-to-use-them-2111a1b692b1
For my answer, try this:
componentDidMount = async () => {
AsyncStorage.getItem('#MyUserFireBase:key').then(response => {
this.setState({ 'isLogin': true });
}).done();
}
Also, if that does not work, try Update instead, since you are setting the state:
componentDidUpdate() = {
AsyncStorage.getItem('#MyUserFireBase:key').then(response => {
this.setState({ 'isLogin': true });
}).done();
}
Use componentDidMount() instead of componentWillMount() and remove async()
componentDidMount() {
AsyncStorage.getItem('#MyUserFireBase:key').then(response => {
this.setState({ 'isLogin': true });
}).done();
}
componentWillMount is also marked as deprecated in React 16.3
https://reactjs.org/blog/2018/03/29/react-v-16-3.html (Lifecycle section)
Related
I am fairly new to React. I have currently made a loading screen in React with useEffect, but I'm not sure how to make it with class Components. This is my functional component which works.
const [sourceLoading, setSourceLoading] = React.useState(true);
// control when to stop loading
useEffect(() => {
setTimeout(() => {
setSourceLoading(false);
}, 1000);
}, [])
return (
<div>
{sourceLoading ? (<LoadingScreen />): (
<>
</>
)}
</div>
);
I'm currently converting the function like so, however it isn't working, and my loading screen never appears. Where am I going wrong? is componentDidMount not the correct substitution for useEffect here?
this.state = {
sourceLoading: true,
};
this.componentDidMount = this.componentDidMount.bind(this);
componentDidMount() {
setTimeout(() => {
this.setState({ sourceLoading: false});
}, 1000);
}
render() {
return (
<div>
{this.sourceLoading ? (<LoadingScreen />) : (<>
</>
)}
</div>
);
}
It works for me, if you change this line:
{this.sourceLoading ? (content) : ""}
// should be
{this.state.sourceLoading ? (content) : ""}
class App extends React.Component {
constructor() {
super();
this.state = {
sourceLoading: true,
};
}
componentDidMount() {
setTimeout(() => {
this.setState({
sourceLoading: false
});
}, 1000);
}
render() {
return (
<div>
{this.state.sourceLoading ? "loading" : "not"}
</div>
);
}
}
ReactDOM.render( <App /> , document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
You need to access the state in render function like
{this.state.sourceLoading ? (<LoadingScreen />) : null}
I am new in React and I will appreaciate much any help. I am using create-react-app, react-router-dom and express server. When I try to submit a comment to a blog post (child component called Details), it gets stored in the database, however the component does not seem to update and i do not see the new comment.As a result, I can see the new comment only after i refresh the page but not on form submit. I guess I am not setting componentDidUpdate properly but I do not have a clue how to do it, so i can see the comment immediately.
Here is my App.js:
class App extends Component {
constructor(props) {
super(props)
this.state = {
userId: null,
username: null,
isAdmin: false,
isAuthed: false,
jwtoken: null,
posts: [],
filtered: [],
}
this.handleSubmit = this.handleSubmit.bind(this)
}
static authService = new AuthService();
static postService = new PostService();
static commentService = new CommentService();
componentDidMount() {
const isAdmin = localStorage.getItem('isAdmin') === "true"
const isAuthed = !!localStorage.getItem('username');
if (isAuthed) {
this.setState({
userId: localStorage.getItem('userId'),
username: localStorage.getItem('username'),
isAdmin,
isAuthed,
})
}
this.getPosts()
}
componentDidUpdate(prevProps, prevState, posts) {
if (prevState === this.state) {
this.getPosts()
}
}
handleChange(e, data) {
this.setState({
[e.target.name]: e.target.value
})
}
handleCommentSubmit(e, data) {
e.preventDefault();
e.target.reset();
App.commentService.createComment(data)
.then(body => {
this.getposts()
if (!body.errors) {
toast.success(body.message);
}
else {
toast.error(body.message);
}
}
)
.catch(error => console.error(error));
}
getPosts() {
App.postService.getPost()
.then(data => {
this.setState({
posts: data.posts.length? data.posts : []
});
}
)
.catch(e => this.setState({ e }))
}
render() {
return (
<Fragment>
<Header username={this.state.username} isAdmin={this.state.isAdmin} isAuthed={this.state.isAuthed} logout={this.logout.bind(this)} />
<Switch>
<Route exact path="/" render={(props) => (
<Home
posts={this.state.posts}
handleSearchSubmit={this.handleSearchSubmit.bind(this)}
handleChange={this.handleSearchChange.bind(this)}
{...props} />
)} />
<Route path="/posts/:id" render={(props) =>
<Details handleSubmit={this.handleCommentSubmit.bind(this)}
isAdmin={this.state.isAdmin}
isAuthed={this.state.isAuthed}
posts={this.state.posts}
handleChange={this.handleChange}
{...props} />} />
</Switch>
<Footer posts={this.state.posts} formatDate={this.formatDate} />
</Fragment>
);
}
}
export default withRouter(App);
Here is my Details.js:
class Details extends Component {
constructor(props) {
super(props);
this.state = {
post: null,
comment: null
}
this.handleChange = props.handleChange.bind(this);
}
componentDidMount() {
const { posts, match } = this.props;
this.setState({
post: posts.length
? posts.find(p => p._id === match.params.id)
: null,
userId: localStorage.getItem('userId')
})
}
componentDidUpdate(prevProps) {
const { posts, match, isAuthed } = this.props;
if (JSON.stringify(prevProps) === JSON.stringify(this.props)) {
return;
}
this.setState({
post: posts.length
? posts.find(p => p._id === match.params.id)
: null
});
}
render() {
const { post } = this.state;
const { isAdmin, isAuthed } = this.props;
if (!post) {
return <span>Loading post ...</span>;
}
return (
<section className="site-section py-lg">
<form onSubmit={(e)=> this.props.handleSubmit(e, this.state)} className="p-5 bg-light">
<div className="form-group">
<label htmlFor="message">Message</label>
<textarea name="comment" id="message" onChange={this.handleChange} cols={30} rows={10} className="form-control" defaultValue={ ""} />
</div>
<div className="form-group">
<input type="submit" defaultValue="Post Comment" className="btn btn-primary" />
</div>
</form>}
</section>
);
}
}
export default Details;
Any help will be much appreciated!
You are doing a mistake that will be done by any new React developer. Just remember one thing that:-
UI is a function of state
So your UI will only be updated if your state is update.
After submitting a comment don't fetch all your comments again, just concat your new comment to current state and you will see your comment as soon as you submit it successfully
I have a function that render LoginPage if the user is not logged and render the IndexPage if is logged, but It is not rendering none, I tried alerting the user.displayName and It work. See my code.
renderPage = () => {
firebase.auth().onAuthStateChanged(user => {
if (user) {
return <IndexPage />;
} else {
return <LoginPage />;
}
});
};
render() {
return <div>{this.renderPage()}</div>;
}
Why is not working?
You miss a return in the renderPage function, but performing async requests in render is not a good approach in react.
What you should do, is to move the user into the state, then on componentDidMount fetch the user from your async code, and inside your render use the state prop user.
So your code should be something like:
constructor() {
this.state = { user: null };
}
componentDidMount() {
firebase.auth().onAuthStateChanged(user => {
user ? this.setState({ user }) : this.setState({ user: null });
});
}
render() {
const content = this.state.user ? <IndexPage /> : <LoginPage />;
return <div>{content}</div>;
}
Your function inside render method is async function, what you get is undefined.
You should store the user state. Do something like,
class YourComponent extends Component {
constructor(props) {
super(props);
this.state = {
user: null
};
}
componentDidMount() {
firebase.auth().onAuthStateChanged(user => {
if (user) {
this.setState({
user
});
}
});
}
render() {
return (
{this.state.user ? <IndexPage /> : <LoginPage />}
);
}
}
I am setting the state to an empty array, then calling a function to update the state of the component. When I log the state of the function, I am getting the empty array and the updated array at the same time.
Any reason why this is happening and how to only log the updated state.
Following is my code:
class App extends Component {
constructor(props) {
super(props);
this.state = {
videos: [],
};
YTSearch({ key: API_Keys, term: 'USA Top40' }, (videos) => {
this.setState({ videos });
});
}
render() {
return (
<div>
<SearchBar />
{console.log(this.state)}
</div>
)
}
}
Here is the console.log
You shouldn't return console.log in render method!
It's better your code should be:
class App extends Component {
constructor(props) {
super(props);
this.state = { videos: [] };
}
componentDidMount() {
YTSearch({ key: API_Keys, term: 'USA Top40' }, (videos) => {
this.setState({ videos });
});
}
componentDidUpdate(prevProps, prevState) {
const { videos } = this.state;
if(prevState.videos !== videos) {
console.log(videos);
}
}
render() {
return (
<div>
<SearchBar />
</div>
)
}
}
Also, you can place console.log outside of return:
render() {
console.log(this.state);
return (
<div>
<SearchBar />
</div>
)
}
Note: setState({}) is an async process more like a batch process that
runs asynchronously and doesn't block your UI. So you might not see
the updated results in the console right after you change the state.
I agree with #souroush's answer as it is recommended way of doing things. But one way to achieve what you're trying to do is make a function and console logging the state into it
logState = () =>{
console.log("State" , this.state)
}
render() {
return (
<div>
<SearchBar />
{this.logState()}
</div>
)}
I have parent component and child(listView) component. My target is to send backend dispatched data from parent to child. I achieve that via button click. Problem is that child renders after second parent button click. Maybe my mistake is somewhere in componentWillReceiveProps?
Parent:
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
dataSource: '',
noRepos: false
};
}
componentWillReceiveProps(newProps) {
this.setState({
dataSource: newProps.data
});
this.validateData(newProps)
}
submitUsername() {
if(this.validateInput()){
this.props.dispatch({type: 'DUMMY', state: this.state.username});
} else {
this.setState({emptyInput: true});
}
}
render() {
return (
...
<View>
<ListViewComponent dataRecieved={this.state.dataSource} />
</View>
...
);
}
}
export default connect(state => {
return {
data: state.data,
};
})(Parent);
Child:
export default class ListViewComponent extends Component {
constructor(props) {
super(props);
this.state = {
dataSource: new ListView.DataSource({
rowHasChanged: (r1, r2) => r1 !== r2,
}),
};
}
propTypes: {
dataRecieved: PropTypes.func.string
};
componentWillReceiveProps(newProps) {
this.setState({
dataSource: this.state.dataSource.cloneWithRows(this.props.dataRecieved),
});
}
renderRow(rowData) {
return (
<View>
<Text>{rowData.name}</Text>
</View>
);
}
render() {
return (
<View>
<ListView dataSource={this.state.dataSource}
enableEmptySections={true}
renderRow={this.renderRow} />
</View>
);
}
}
Alright, a general advice is to always keep a single source of truth:
do not copy the data that you already have in props to your internal component state. Use the data in props.
try to create your components as stateless as possible (see above...use props, or have the component listen to a 'store'. See Redux or AltJs).
Specifically to try to solve your issue:
In parent replace:
<ListViewComponent dataRecieved={this.state.dataSource} />
with
<ListViewComponent dataRecieved={this.props.data} />
And in ListViewComponent, don't do:
this.setState({
dataSource: this.state.dataSource.cloneWithRows(this.props.dataRecieved),
});
but do:
render() {
var ds = new ListView.DataSource({
rowHasChanged: (r1, r2) => r1 !== r2,
})
, dataSource = ds.cloneWithRows(this.props.dataRecieved);
return (
<View>
<ListView dataSource={dataSource}
enableEmptySections={true}
renderRow={this.renderRow} />
</View>
);
}
The above is untested code, but should serve as a guide to what approach to follow.
The way you originally implemented ListViewComponent is fine, and you SHOULD be using componentWillReceiveProps when refreshing your ListView. Every best practice out there says to do this (just Google react native redux listview). You just have a slight error in your implementation that I mentioned in a comment above. Also, you should not be recreating the ListView.DataSource inside the render function, that is not good for performance and defeats the purpose of rowHasChanged in ListView.DataSource.
The error that I'm talking about is here:
componentWillReceiveProps(newProps) {
this.setState({
dataSource: this.state.dataSource.cloneWithRows(this.props.dataRecieved),
});
}
it should be:
// ListViewComponent
componentWillReceiveProps(newProps) {
this.setState({
dataSource: this.state.dataSource.cloneWithRows(newProps.dataRecieved),
});
}
Also, your Parent should not be holding a dataSource in its state, just pass data straight down to ListViewComponent because Redux is passing it as a prop already:
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
// ...
};
}
componentWillReceiveProps(newProps) {
// ...
}
submitUsername() {
if (this.validateInput()) {
this.props.dispatch({ type: 'DUMMY', ... });
} else {
// ...
}
}
render() {
return (
...
<View>
{/* this.props.data automatically comes from the `connect` wrapper below */}
<ListViewComponent dataRecieved={this.props.data} />
</View>
...
);
}
}
export default connect(state => {
return {
data: state.data,
};
})(Parent);
You should also take a look at this gist. It is an example of how to use Redux alongside a ListView. His example uses cloneWithRowsAndSections, but because you don't have sections, you just adapt with cloneWithRows instead. This gist was written by a pretty active core React Native developer.