I am new to the react and redux. Here is, what I am doing:
I have a component which is like ,
class LandingPage extends React.Component {
constructor(props) {
super(props);
this.state = {
isloading: true
}
}
componentDidMount() {
this.props.fetchJobDescription().then(() => {
this.setState({
isloading: false
})
});
}
render() {
if (this.state.isloading) {
return null;
}
else if (this.props.jobs && this.props.jobs.content && this.props.jobs.content.length > 0) {
return <JobList />;
}
else if (this.props.isError) {
return <ErrorComponent />
}
else {
return <Redirect to="/create-job" />
}
}
}
the action is like ,
export function fetchUserJd() {
return (dispatch) => {
let url = FETCH_JD_ROOT_URL + page + "&" + size;
dispatch({
type: REQUEST_INITIATED
})
return get(url)
.then((response) => {
if (response.status === 200) {
dispatch({
type: FETCHING_JOBDESCRIPTION_SUCCESS,
data: response.payload
})
dispatch({
type: REQUEST_SUCCESSED
})
} else {
if (!response.status) {
toastr.error('Our server is down. Please check again');
}
else if (response.status.status === 401) {
dispatch(logout());
}
else if (response.status.status === 500) {
toastr.error("Error while Fetching the job description,Please try again");
dispatch({
type: FETCHING_JOBDESCRIPTION_SUCCESS,
data: response.status,
});
dispatch({
type: REQUEST_SUCCESSED
})
} else {
dispatch({
type: REQUEST_SUCCESSED
})
}
}
})
return Promise.resolve();
}
};
Now,my logout is ,
export function logout() {
console.log("calling the logout action");
localStorage.clear();
history.push('/login');
return {
type: LOGOUT_REQUEST
}
}
class Header extends React.Component {
constructor(props) {
super(props);
}
logout = (e) => {
e.stopPropagation();
e.nativeEvent.stopImmediatePropagation();
e.preventDefault();
this.props.logout();
}
render() {
return (
<Fragment>
<Navigation
isAuthenticated={localStorage.getItem("access_token") ? true : false}
operationType={this.props.operationType}
logout={this.logout} />
</Fragment>
)
}
}
const mapStateToProps = (state) => {
return {
isAuthenticated: state.LoginReducer.isAuthenticated,
operationType: state.Header.operationType,
}
}
Here, when there is a invalid token like while fetching it gives me 401 unauthorized, then I redirect use for the logout action. now,
when I do this that time , I get an error:
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
in LandingPage (created by Context.Consumer)
in Connect(LandingPage) (created by Route)
How I can resolve this error ?
The issue is because you are setting state after component has unmounted. The issue might be that you make api hit, component unmounts, Then response of api is returned which sets the state. If you are using axios it can be handled.
// in the component
signal = axios.CancelToken.source();
// in componentWillUnmount
this.signal.cancel('API was cancelled');
Its a small issue that happens in your code. When you receive a 401 token, you try to redirect to logout from within the action creator using history.push which will unmount your LandingPage component, but at the same time you are trying to setState with loading: false, thus you receive this warning. The solution is simple
class LandingPage extends React.Component {
constructor(props) {
super(props);
this.state = {
isloading: true
}
this._mounted = true;
}
componentDidMount() {
this.props.fetchJobDescription().then(() => {
if (this_mounted) {
this.setState({
isloading: false
})
}
});
}
componentWillUnmount() {
this._mounted = false;
}
render() {
if (this.state.isloading) {
return null;
}
else if (this.props.jobs && this.props.jobs.content && this.props.jobs.content.length > 0) {
return <JobList />;
}
else if (this.props.isError) {
return <ErrorComponent />
}
else {
return <Redirect to="/create-job" />
}
}
}
or else in the action creator you can throw an error instead of dispatching the logout action and in the .catch block of fetchJobDescription dispatch the logout action
In LandingPage
this.props.fetchJobDescription().then(() => {
this.setState({
isloading: false
})
}).catch((err) => {
this.props.logout();
});
and in action creator
else if (response.status.status === 401) {
throw new Error('Error in status')
}
Related
I am consoling state right after my function call in componentDidMount but it's giving data as EMPTY String.
import React, { Component } from "react";
class App extends Component {
constructor(props) {
super(props);
this.state = {
data: ""
};
}
getData = () => {
functionApiCall().then(res => {
this.setState({
data: res.data
}); // Here the state is getting set
})
}
componentDidMount() {
this.getData();
console.log(this.state.data); //Empty string
}
render() {
return <></>;
}
}
export default App;
Any help will be appreciated.Thank you
Well, I think the api call is returning null , maybe change it like this
getData = () => {
functionApiCall().then(res => {
if(res && res.data) {
this.setState({
data: res.data
})// Here the state is getting set
}
}
}
Above should be fine, but just in case try this
getData = () => {
return new Promise(function(resolve, reject) {
functionApiCall().then(res => {
if(res && res.data) {
this.setState({
data: res.data
}, () => { resolve(res.data) })// Here the state is getting set
}
} });
}
And componentDidMount wait for your promise which resolves after state is set
async componentDidMount(){
await this.getData();
console.log(this.state.data) //NULL
}
setState is asynchronous so you cannot immediately access it.
You can render conditionally like this:
class App extends Component {
constructor(props) {
super(props);
this.state = {
data: null
};
}
getData = () => {
functionApiCall().then(res => {
this.setState({
data: res.data
});
});
};
componentDidMount() {
this.getData();
}
render() {
if (!this.state.data) {
return <div>Loading...</div>;
} else {
return <div>Data: {JSON.stringify(this.state.data)}</div>;
}
}
}
export default App;
Sample codesandbox with a fake api
I'm trying to do a basic API fetch and show that information onClick using a button called GENERATE. All it should do for now is show the first url in the json I receive.
Once that is achieved, I want it to show the next url on each click.
App.js
import React, { Component } from 'react';
import { ThemeProvider, createToolkitTheme } from 'internaltools/theme';
import { AppHeader } from 'internaltools/app-header';
const LIGHT_THEME = createToolkitTheme('light');
const DARK_THEME = createToolkitTheme('dark');
const API = 'https://hn.algolia.com/api/v1/search?query=';
const DEFAULT_QUERY = 'redux';
class App extends Component {
constructor(props) {
super(props);
this.state = {
hits: [],
isLoading: false,
error: null,
};
}
componentDidMount(){
this.setState({ isLoading: true });
fetch(API + DEFAULT_QUERY)
.then(response => {
if (response.ok) {
return response.json();
} else {
throw new Error('Something went wrong with the API...');
}
})
.then(data => this.setState({ hits: data.hits[0], isLoading: false }))
.catch(error => this.setState({ error, isLoading: false }));
}
render() {
const { hits, isLoading, error } = this.state;
return (
<>
<button onClick={hits.url}>GENERATE</button>
</>
);
}
}
Please help me find out why my button doesn't work. And how do I iterate over the urls on each click, i.e. show the next url from the json on each click. Thanks.
You should pass a function name to your onClick handler. Then in that function you can access the data you wanted.
enter code here
import React, { Component } from 'react';
import { ThemeProvider, createToolkitTheme } from 'internaltools/theme';
import { AppHeader } from 'internaltools/app-header';
const LIGHT_THEME = createToolkitTheme('light');
const DARK_THEME = createToolkitTheme('dark');
const API = 'https://hn.algolia.com/api/v1/search?query=';
const DEFAULT_QUERY = 'redux';
class App extends Component {
constructor(props) {
super(props);
this.state = {
hits: [],
isLoading: false,
error: null,
hitsCount: 0
};
this.handleClick = this.handleClick.bind(this);
}
componentDidMount(){
this.setState({ isLoading: true });
fetch(API + DEFAULT_QUERY)
.then(response => {
if (response.ok) {
return response.json();
} else {
throw new Error('Something went wrong with the API...');
}
})
.then(data =>
this.setState({ hits: data.hits, hitsCount: 0 ,isLoading: false
}))
.catch(error => this.setState({ error, isLoading: false }));
}
handleClick(){
this.setState(prevState => ({ hitsCount: prevState.hitsCount + 1
}));
}
render() {
const { hits, hitsCount, isLoading, error } = this.state;
return (
<>
<div>
count: {hitsCount}
url: {hits[hitsCount].url}
</div>
<button onClick={this.handleClick}>GENERATE</button>
</>
);
}
}
You need to pass an onClick handler function to update a state value.
Here's a codesandbox that stores the hits array in state along with a current index, and a handler that simply increments the index.
Consider This:
Read through the comments in the code to get the updates.
class App extends Component {
constructor(props) {
super(props);
this.state = {
hits: [],
currentHit: 0, //add a state currentHit to hold the url that is displayed by now
isLoading: false,
error: null,
};
}
componentDidMount(){
this.setState({ isLoading: true });
fetch(API + DEFAULT_QUERY)
.then(response => {
if (response.ok) {
return response.json();
} else {
throw new Error('Something went wrong with the API...');
}
})
.then(data => this.setState({ hits: data.hits, isLoading: false })) //Make hits array holding all the hits in the response instead of only the first one
.catch(error => this.setState({ error, isLoading: false }));
}
handleClick = () => {
this.setState(prevState => ({
currentHit: prevState.currentHit + 1,
}));
}
render() {
const { hits, isLoading, error, currentHit } = this.state;
// pass the handleClick function as a callback for onClick event in the button.
return (
<>
<p>{hits[currentHit].url}<p/>
<button onClick={this.handleClick.bind(this)}>GENERATE</button>
</>
);
}
}
Here is the working code, on each click next url will be shown.
codesandbox link
handleChange method can work if you want to append the url from array as well. Or you could just increment the index in this function.
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
class App extends React.Component {
state = {
data: [],
index: 0
};
componentDidMount() {
this.setState({ isLoading: true });
fetch("https://reqres.in/api/users")
.then(response => {
if (response) {
return response.json();
} else {
throw new Error("Something went wrong with the API...");
}
})
.then(data => this.setState({ data: data.data }))
.catch(error => this.setState({ error }));
}
handleChange = () => {
let i =
this.state.index < this.state.data.length ? (this.state.index += 1) : 0;
this.setState({ index: i });
};
render() {
return (
<div className="App">
<span>
{this.state.data.length && this.state.data[this.state.index].avatar}
</span>
<button onClick={this.handleChange}>GENERATE</button>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
I'm trying to implement socket.io library to a React application for practice. It's my first time I implement redux in any kind for application.
Describe the problem
I created a component Chatroom.js where in componentDidMount I dispatch an action to connect to the socket and listen for events.
componentDidMount() {
console.log('---Chatroom did mount')
console.log('isLoaded: ' + this.props.socket.isLoaded)
// if I remove this if-statement the compoenent re-renders
// and a new socket is created
if (!this.props.socket.isLoaded) {
this.props.userLoginToSocket()
.then(() => this.props.receive())
.then(() => this.props.setLoaded(true))
.then(() => this.props.sendUsername(this.props.auth.user.username))
}
}
I implemented a redux middleware to handle the socket.io communication as proposed in this post.
When I start the application I get this log
Chatroom.js:55 ---Chatroom did mount
Chatroom.js:58 isLoaded: false
Chatroom.js:76 ---Chatroom will unmount
Messages.js:38 Messages component didMount
Chatroom.js:55 ---Chatroom did mount
Chatroom.js:58 isLoaded: true
And the componentWillReceiveProps never gets executed.
I don't think that this is the expected behaviour and the componentDidMount should be only called once. Furthermore I cannot understand why the componentWillUnmount gets fired.
When I recieve a message from the server the log is
Chatroom.js:76 ---Chatroom will unmount
Messages.js:38 Messages component didMount
Chatroom.js:55 ---Chatroom did mount
Chatroom.js:58 isLoaded: true
which clearly indicates that every time I dispatch an action the component unmounts and remounts.
Full Code
You can find the full project's code at github.
// ./Chatroom.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
import { userLoginToSocket, receive, sendUsername, disconnect, setLoaded, emit } from '../actions/socketAction';
import Messages from './Messages'
class Chatroom extends Component {
constructor(props) {
super(props)
this.handleSend = this.handleSend.bind(this)
}
componentDidMount() {
console.log('---Chatroom did mount')
// console.log('socket.isConnected: ' + this.props.socket.isConnected)
// console.log('socket.isConnecting: ' + this.props.socket.isConnecting)
console.log('isLoaded: ' + this.props.socket.isLoaded)
// if I remove this if-statement the compoenent re-renders
// and a new socket is created
if (!this.props.socket.isLoaded) {
this.props.userLoginToSocket()
.then(() => this.props.receive())
.then(() => this.props.setLoaded(true))
.then(() => this.props.sendUsername(this.props.auth.user.username))
}
}
componentWillUnmount() {
// every time a new message is recieved the component willUnmount
// i want on component will unmount to disconnect from the socket
console.log('---Chatroom will unmount')
}
componentWillReceiveProps(nextProps) {
console.log('---Component will receive props')
}
handleSend() {
this.props.emit('Hello')
}
render() {
return (
<div>
<h1>Chatroom</h1>
{ this.props.socket.isLoaded &&
<Messages messages={this.props.messages.messages}/>
}
<button onClick={this.handleSend}>Send</button>
</div>
);
}
}
Chatroom.propTypes = {
socket: PropTypes.object,
messages: PropTypes.object
}
const mapStateToProps = (state) => {
return({
socket: state.socket,
messages: state.messages
})
}
export default withRouter(
connect(mapStateToProps, { userLoginToSocket , receive, sendUsername, disconnect, setLoaded, emit })(Chatroom)
)
// ./Messages.js
import React, { Component } from 'react'
class Messages extends Component {
componentDidMount() {
console.log('Messages component didMount')
}
render() {
return (
<div>
<h3>Messages</h3>
<ul>
{this.props.messages.map((item, ind) => <li key={ind}>{item.message}</li>)}
</ul>
</div>
)
}
}
export default Messages
Actions
// ../actions/socketAction
export const setLoaded = (boolean) => {
return {
type : "SET_LOADED",
boolean
}
}
export const userLoginToSocket = () => {
return (dispatch) => {
return dispatch({
type: 'socket',
types: ["CONNECT", "CONNECT_SUCCESS", "CONNECT_FAIL"],
promise: (socket) => socket.connect()
});
}
}
export function disconnect() {
return {
type: 'socket',
types: ["DISCONNECT", "DISCONNECT_SUCCESS", "DISCONNECT_FAIL"],
promise: socket => socket.disconnect(),
}
}
export const receive = () => {
return (dispatch) => {
const newMessage = (message) => {
return dispatch({
type: "NEW_MESSAGE_FROM_SOCKET",
payload: message,
});
};
return dispatch({
type: 'socket',
types: ["RECEIVE_", "RECEIVE_SUCC", "RECEIVE_FAIL"],
promise: (socket) => socket.on('ReceiveMessage', newMessage),
});
}
}
export const sendUsername = (user) => {
return (dispatch) => {
return dispatch({
type: 'socket',
types: ["SEND_USER", "SEND_USER_SUCCESS", "SEND_USER_FAIL"],
promise: (socket) => socket.emit('SET_USERNAME', user),
});
}
}
export const emit = (message) => {
return (dispatch) => {
return dispatch({
type: 'socket',
types: ["SEND", "SEND_SUCCESS", "SEND_FAIL"],
promise: (socket) => socket.emit('SEND_MESSAGE', message),
});
}
}
Reducers
// ../socketReducer.js
const initialState = {
isConnected: false,
isConnecting: false,
isLoaded: false,
messageRecieved: false
}
export default function socketReducer(state=initialState, action) {
switch (action.type) {
case "CONNECTED":
return {
...state,
isConnected: true
}
case "DISCONNECTED":
return {
...state,
isConnected: false
}
case "NEW_MESSAGE_FROM_SOCKET":
return {
...state,
messageRecieved: true
}
case "SET_LOADED":
return {
...state,
isLoaded: action.boolean
}
default:
return state
}
}
// ../messagesReducer.js
const initialState = {
messages: [{message: "initial"}],
isRecieving: false,
didRecieve: false
}
export default function(state = initialState, action) {
switch (action.type) {
case "NEW_MESSAGE_FROM_SOCKET":
return {
...state,
messages: [...state.messages, action.payload]
}
default :
return state
}
}
Keep getting the following error message in React Native, really don't understand where it is coming from
Warning: Can't call setState (or forceUpdate) on an unmounted
component. This is a no-op, but it indicates a memory leak in your
application. To fix, cancel all subscriptions and asynchronous tasks
in the componentWillUnmount method.
I have the following simple component:
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
isLoggedIn: false,
}
}
componentDidMount(){
this.fetchToken()
}
async fetchToken(){
const access_token = await AsyncStorage.getItem('access_token')
if (access_token !== null) {
this.setState({ isLoggedIn: true })
}
}
render() {
const login = this.state.isLoggedIn
if (login) {
return <NavigatorLoggedIn />
} else {
return <Navigator/>
}
}
}
You can use it:
componentDidMount() {
this.function()
}
function = async () => {
const access_token = await AsyncStorage.getItem('access_token')
if (access_token !== null) {
this.setState({ isLoggedIn: true })
}
}
Or you can call function in constructor.
I hope this will help you...
It's will be work:
let self;
class App extends React.Component {
constructor(props) {
super(props)
self = this;
this.state = {
isLoggedIn: false,
}
}
componentDidMount(){
this.fetchToken()
}
async fetchToken(){
const access_token = await AsyncStorage.getItem('access_token')
if (access_token !== null) {
self.setState({ isLoggedIn: true })
}
}
render() {
const login = self.state.isLoggedIn
if (login) {
return <NavigatorLoggedIn />
} else {
return <Navigator/>
}
}
}
you need use isMounted variable.
componentDidMount(){
this.setState({ isMounted = true });
const access_token = await AsyncStorage.getItem('access_token')
if (access_token !== null && this.isMounted) {
this.setState({ isLoggedIn: true })
}
}
componentWillUnmount(){
this.setState({ isMounted = false });
}
Or if you use Axios, you can use cancel request feature of axios
this here: https://github.com/axios/axios#cancellation
You can try this:
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
isLoggedIn: false,
}
}
_isMounted = false;
componentDidMount(){
this._isMounted = true;
this.fetchToken()
}
async fetchToken(){
const access_token = await AsyncStorage.getItem('access_token')
if (access_token !== null && this._isMounted) {
this.setState({ isLoggedIn: true })
}
}
componentWillUnmount() {
this._isMounted = false;
}
render() {
const login = this.state.isLoggedIn
if (login) {
return <NavigatorLoggedIn />
} else {
return <Navigator/>
}
}
}
By using _isMounted, setState is called only if component is mounted, The unmounting doesn't wait for the async call to finish. Eventually when the async call gets over, the component is already unmounted and so it cannot set the state. To do this, the answer simply does a check to see if the component is mounted before setting the state.
Cancel all the async operation is one of the solution
For me, I resolved it by restart the server by "yarn start" or "npm start"
I am using nested route. Parent component shows category list and the child component shows modal. On performing delete action in modal i.e. child, I am redirecting to the parent route using history.push. The component is getting rendered but without re-rendering the component i.e. the record which was deleted still appears in the view, but when I refresh the page the record does not appear. Any suggestions on this?
Here is my code -
CategoryList.js
import React, { Component } from 'react';
import { Route, Link } from 'react-router-dom';
import CustomisedModal from './../../elements/CustomisedModal';
class CategoryList extends Component {
state = {
categories: [],
}
componentDidMount() {
fetch('http://localhost:8080/xxxx')
.then(results => {
return results.json();
}).then(data => {
this.setState({ categories: data.categories });
})
.catch(error => {
this.setState({ error: true });
});
}
deleteCategoryHandler = (id) => {
**//navigate to modal**
this.props.history.replace('/category/delete/' + id);
}
render() {
if (!this.state.error) {
categories = this.state.categories.map(category => {
return (
xxxxx
)
})
}
return (
<Container>
xxxx
<Route path="/category/delete/:id" exact component={DeleteCategory} /> **<!--Nested route-->**
</Container>
);
}
}
export default CategoryList;
CustomisedModal.js
import React from 'react'
import { Button, Header, Modal, Icon } from 'semantic-ui-react';
class CustomisedModal extends React.Component {
constructor(props) {
super(props);
this.state = {
showModal: this.props.props
}
}
onClose = () => {
this.props.props.history.go('/category');
}
deleteCategory = () => {
fetch('http://localhost:8080/xxxx/' + this.props.props.match.params.id , {
method: 'delete'
})
.then(results => {
return results.json();
}).then(data => {
**//Redirect to parent route**
this.props.props.history.go('/category');
})
.catch(error => {
this.setState({ error: true });
});
}
render() {
return (
<Modal> xxxx
</Modal>
)
}
}
export default CustomisedModal;
the problem here is your parent component has the fetch call in componentDidMount. use componentWillReceiveProps with some condition to reload the data after delete action. As the current parent state hold the old data.
Hope this will help
Fixed the issue by updating state on receiving response so that component gets re-rendered
CustomisedModal.js
import React from 'react'
import { Button, Header, Modal, Icon } from 'semantic-ui-react';
class CustomisedModal extends React.Component {
constructor(props) {
super(props);
this.state = {
categories: this.props.props.categories **added categories to state**
}
}
onClose = () => {
this.props.props.history.go('/category');
}
removeByAttr = function (arr, attr, value) {
var i = arr.length;
while (i--) {
if (arr[i]
&& arr[i].hasOwnProperty(attr)
&& (arguments.length > 2 && arr[i][attr] === value)) {
arr.splice(i, 1);
}
}
return arr;
}
deleteCategory = () => {
fetch('http://localhost:8080/xxxx/' + this.props.props.match.params.id , {
method: 'delete'
})
.then(results => {
return results.json();
}).then(data => {
**//Redirect to parent route**
let newArray = this.removeByAttr(this.state.categories, 'id', data.id);
this.setState({ categories: newArray }); **//updated setState**
this.props.props.history.go('/category');
})
.catch(error => {
this.setState({ error: true });
});
}
render() {
return (
<Modal> xxxx
</Modal>
)
}
}
export default withRouter(CustomisedModal);**// added withRouter**