React Socket.io: Can only update a mounted or mounting component - reactjs

I'm getting repeated warnings
Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the RoundView component.
and it points to a socket.io line
socket.on(
'rankings',
(payload) => this.setState({
totals: payload.totals,
scores: payload.scores
},this.updateRoundProgress)
);
But the component is clearly mounted and everything seems to function fine, the state gets updated just fine when a 'rankings' event is captured. The full code is below:
https://github.com/JellyKid/ballsavr/blob/master/Frontend/app/components/user/RoundView.jsx
import React from 'react';
import handleFetch from '../../helpers/handleFetch';
import { Grid, Col, PageHeader } from 'react-bootstrap';
import { connect } from 'react-redux';
import update from 'immutability-helper';
import ConfirmScores from './view/ConfirmScores';
import Rankings from './view/Rankings';
import Scores from './view/Scores';
import Group from './view/Group';
import Manage from './view/Manage';
const socket = require('socket.io-client')('/round',{
path: '/api/socket.io',
autoConnect: false
});
class RoundView extends React.Component {
constructor(props) {
super(props);
this.state = {
round: this.props.round || {
event: {
title: 'Loading...'
},
name: "Please wait",
tables: [],
players: []
},
scores: [],
totals: []
};
this.refreshScores = this.refreshScores.bind(this);
this.updateRoundProgress = this.updateRoundProgress.bind(this);
}
componentDidMount(){
this.handleSocket();
let fetches = [
handleFetch('GET',`/api/score/totals?round=${this.props.params.roundID}`),
handleFetch('GET',`/api/score/round?id=${this.props.params.roundID}`)
];
if(!this.props.round){
fetches.push(handleFetch('GET', `/api/round/${this.props.params.roundID}`));
}
return Promise.all(fetches)
.then(
(res) => {
this.setState({
totals: res[0].payload,
scores: res[1].payload,
round: this.props.round || res[2].payload
});
}
).catch((err) => {console.log(err);});
}
handleSocket(){
socket.open();
socket.on(
'connect',
() => socket.emit('join round', this.props.params.roundID)
);
socket.on(
'rankings',
(payload) => this.setState({
totals: payload.totals,
scores: payload.scores
},this.updateRoundProgress)
);
socket.on(
'connect_error',
() => setTimeout(socket.open, 1000)
);
}
updateRoundProgress(){
if(this.props.player.admin){
let scores = this.state.scores.filter((score) => score.confirmed).length;
let progress = Math.floor((scores/(this.state.round.tables.length * this.state.round.players.length))*100);
return this.setState(update(
this.state,
{round: {progress : {$set: progress}}}
));
}
}
refreshScores(){
handleFetch('GET',`/api/score/round?id=${this.props.params.roundID}`)
.then(
(res) => this.setState({scores: res.payload})
).catch((err) => {console.log(err);});
}
componentWillUnmount(){
socket.close();
}
render(){
let player = this.state.round.players.find((p) => p.user._id === this.props.player._id);
let groupName = (player) ? player.group : "";
return (
<Grid>
<Col md={6} mdOffset={3}>
<PageHeader>
{this.state.round.event.title}<br/><small>{this.state.round.name}</small>
</PageHeader>
<Rankings
totals={this.state.totals}
players={this.state.round.players}
player={this.props.player}
/>
<hr />
<Group
group={groupName}
players={this.state.round.players}
/>
<hr />
<Scores
player={this.props.player}
scores={this.state.scores}
round={this.state.round}
group={groupName}
/>
<hr />
<ConfirmScores
scores={this.state.scores}
player={this.props.player}
/>
<hr />
<Manage
round={this.state.round}
player={this.props.player}
/>
</Col>
</Grid>
);
}
}
function mapStateToProps(state, ownProps) {
return {
round: state.rounds.find((round) => round._id === ownProps.params.roundID),
player: state.user
};
}
export default connect(mapStateToProps)(RoundView);
I close the socket when the component unmounts. I also tried logging when the component unmounts and it doesn't unmount before this so why the heck am I getting the error?

This probably related to the fact your component is connected to Redux
as well, it means there's another instance (that's not mounted) that's
listening to the socket. - My guess is to try to disconnect redux and
see if that helps. in any case, i would move the socket and optionally
redux to a container component, there's too much going on on this
component to debug it easily! – Patrick
Thanks Patrick!

Related

Why is this.state not updated real-time after setState is issued?

So I'm trying to simulate the state by clicking a button. The 'before' status seems to have the correct value, but why is the 'after' not displaying the correct value even if the setState is already hit by the code?
class App extends Component {
constructor(){
super()
this.state = {isLoggedIn: false}
this.OnClick = this.OnClick.bind(this);
}
OnClick(){
this.setState(prev =>
{
return (prev.isLoggedIn = !this.state.isLoggedIn);
})
console.log(`After setState value: ${this.state.isLoggedInstrong text}`) // setState is done, why is this.state displaying incorrect value?
}
render()
{
console.log(`Before setState value: ${this.state.isLoggedIn}`)
return <Login isLoggedIn={this.state.isLoggedIn} OnClick={this.OnClick} />
}
}
import React from "react";
class Login extends React.Component
{
render()
{
const {isLoggedIn, OnClick} = this.props;
return (
<div>
<button onClick={OnClick} >{isLoggedIn ? "Log Out" : "Log In"} </button>
</div>
)
}
}
export default Login;
OUTPUT:
"Before setState value: false"
(Initial display, button value is: Log In)
When button is clicked:
"After setState value: false" <------ why false when setState has been hit already? Not real-time update until Render is called?
"Before setState value: true"
(Button value is now: Log Out)
The main problem I see in your code is you’re trying to mutate the state.
this.setState(prev => {
return (prev.isLoggedIn = !this.state.isLoggedIn);
})
You have to merge to the state not mutate it. You can do it simply by returning an object like this.
this.setState((prev) => {
return { isLoggedIn: !prev.isLoggedIn };
});
This will fix all the weird behaviours in your code.
Full Code
App.js
import { Component } from "react";
import Login from "./Login";
class App extends Component {
constructor() {
super();
this.state = { isLoggedIn: false };
this.OnClick = this.OnClick.bind(this);
}
OnClick() {
this.setState((prev) => {
return { isLoggedIn: !prev.isLoggedIn };
});
console.log(`After setState value: ${this.state.isLoggedIn}`);
}
render() {
console.log(`Before setState value: ${this.state.isLoggedIn}`);
return <Login isLoggedIn={this.state.isLoggedIn} OnClick={this.OnClick} />;
}
}
export default App;
Login.js
import { Component } from "react";
class Login extends Component {
render() {
const { isLoggedIn, OnClick } = this.props;
return (
<div>
<button onClick={OnClick}>{isLoggedIn ? "Log Out" : "Log In"} </button>
</div>
);
}
}
export default Login;
CodeSandbox - https://codesandbox.io/s/setstate-is-not-update-the-state-69141369-efw46
try this
this.setState({
isLoggedIn:!this.state.isLoggedIn
})
or
this.setState(prev => ({
isLoggedIn:!prev.isLoggedIn
}))

Cannot update during an existing state transition without any setState in render()

I am learning react and I encountered this error and could not find the solution.
The error I recieve is:
Cannot update during an existing state transition (such as within
render). Render methods should be a pure function of props and
state. at clientsDisplay
(http://localhost:3000/static/js/main.chunk.js:672:39) at div at App
(http://localhost:3000/static/js/main.chunk.js:163:5)
The problem is in this code:
import React from 'react'
import Client from './Client/Client'
const clientsDisplay=(props)=>props.clientsArray.map(
(client,index) =>{
return <Client
clientName={client.name}
clientProjDeadline={client.deadline}
clickDel={props.clientDel(index)}
key={client.id}
changed={event=>props.changed(event,client.id)}
len={props.clArraylength}
/>}
)
export default clientsDisplay
The main component which contains the render function looks like this:
import appCss from './App.module.css';
import React,{Component} from 'react';
import ClientsDisplay from './components/ClientHandle/ClientsDisplay';
class App extends Component{
state = {
userName:'Julian',
showPara: false,
clientsArray: [
{
id:"001",
name: "Max",
deadline: "2021/05/17"
},
{
id:"002",
name: "James",
deadline: "2021/12/06"
},
{
id:"003",
name: "Johnny",
deadline: "2021/07/21"
}
]
}
deleteClient = (delClientIndex)=>{
let clientsArrayCopy=[...this.state.clientsArray]
clientsArrayCopy.splice(delClientIndex,1)
this.setState({
clientsArray:clientsArrayCopy
})
}
valueChanger = (event)=>{
this.setState({
userName: event.target.value
})
}
valueChangerClient = (event,id)=>{
let targetInd=this.state.clientsArray.findIndex(elem=>elem.id===id)
let changedClientArray=[...this.state.clientsArray]
changedClientArray[targetInd].name=event.target.value
this.setState({
clientsArray:changedClientArray
})
}
togglePara = ()=>{
this.setState({
showPara: !this.state.showPara
})
}
render(){
let clientArraylength=this.state.clientsArray.length
return(
<div className={appCss.App}>
<ClientsDisplay
clientsArray={this.state.clientsArray}
changed={this.valueChangerClient}
clientDel={this.deleteClient}
clArrayLength={clientArraylength}/>
</div>
)
}
Currently you're actually calling props.clientDel on every render:
clickDel={props.clientDel(index)}
should be
clickDel={() => props.clientDel(index)}

How can I update a single React Component from multiple different components?

I'm learning React and still trying to figure out how to plan out and implement some things. I have an app that makes three different API calls and thus three different return values. I'd like to have a global status component that tells me if all three loaded or not. Here's my psuedo code since I haven't found the proper way to do this yet, but this is effectively my train of thought at the moment. I have the main app:
const App = () => {
return (
<div>
<GenericAPICallerA />
<GenericAPICallerB />
<GenericAPICallerC />
<div>
<APIStatus/>
</div>
</div>
);
}
This is the APIStatus which just returns if all A, B, and C API calls have loaded or not:
class APIStatus extends React.Component{
constructor(props){
super(props);
this.state = {
aLoaded: false,
bLoaded: false,
cLoaded: false,
};
}
render(){
if (this.state.aLoaded && this.state.bLoaded && this.state.cLoaded){
return <div>Everything has loaded!</div>
}
else{
return <div>Nothing has loaded!</div>
}
}
}
And finally one of the APICaller components. The others are essentially the same:
class GenericAPICallerA extends React.Component{
constructor(props){
super(props);
this.state = {
error: null,
isLoaded: false,
};
}
componentDidMount(){
fetch("example.com/api",{
method: 'GET',
})
.then(res => res.json())
.then(
(result) =>{
this.setState({
isLoaded: true,
});
},
(error) =>{
this.setState({
isLoaded: false,
error
});
}
)
}
render(){
const { error, isLoaded, profile } = this.state;
if (error){
return <div>Errored!</div>
} else if (!isLoaded){
// APIStatus.state.aLoaded=false
} else {
// APIStatus.state.aLoaded=true
return(
<div>Generic Caller A is done!</div>
);
}
}
}
The comments in the render section are what I don't know how to do. I feel like I should pass in the APIStatus as a prop to the GenericAPICaller but I'm still unsure how I would update that property from inside the GenericAPICaller.
Thanks!
You can create a function in parent component and pass it to the child will be triggered and pass a state variable to the child where it will be used
For example:
import React from 'react'
import ComponentA from './ComponentA'
import ComponentB from './ComponentB'
class App extends React.Component
{
constructor()
{
super()
this.state = { my_state_variable:`some value` }
}
my_function()
{
this.setState({ my_state_variable:`new value` })
}
render = () => <>
<ComponentA my_function={my_function} />
<ComponentB my_state_variable={my_state_variable} />
</>
}
export default App
ComponentA
import React from 'react'
const ComponentA = ({ my_function }) => <>
<button onClick={() => my_function() }>Click Me </button>
</>
export default ComponentA
ComponentB
import React from 'react'
const ComponentB = ({ my_state_variable }) => <>
<p>{my_state_variable}</p>
{my_state_variable === `some value` && <p>if some value this will render </p>}
{my_state_variable === `new value` && <p>if new value this will render </p>}
</>
export default ComponentA
You can use context to accomplish this. By using context, you are able to access the value you provide to it as long as the component you attempt to access it through is a child of a provider.
The example below illustrates how you can access a shared state between multiple components at different levels without having to pass props down.
const {
useState,
useEffect,
createContext,
useContext,
Fragment
} = React;
const MainContext = createContext({});
function Parent(props) {
const [state, setState] = useState({child1:false,child2:false,child3:false});
return <MainContext.Provider value={{state,setState}}>
<Child1/> {state.child1? 'loaded':'not loaded'}
<br/>
<Child2/>
</MainContext.Provider>;
}
function Child1(){
const {state, setState} = useContext(MainContext);
return <button onClick={()=>setState({...state, child1:!state.child1})}>Load Child 1</button>;
}
function Child2(){
const {state, setState} = useContext(MainContext);
return <Fragment>
<button onClick={()=>setState({...state, child2:!state.child2})}>Load Child 2</button> {state.child2? 'loaded':'not loaded'}
<br/>
<Child3/> {state.child3? 'loaded':'not loaded'}
</Fragment>;
}
function Child3(){
const {state, setState} = useContext(MainContext);
return <button onClick={()=>setState({...state, child3:!state.child3})}>Load Child 3</button>;
}
const el = document.querySelector("#root");
ReactDOM.render(<Parent/>, el);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Why my render method is react called twice

I am quite new to react and redux. This is a little confusing problem for me. Can someone please explain this why my searchTerm state value printed twice (render method is getting called twice) after every input change. I read react and learnt that on every state change, render is called but in this called render is called twice? Am I getting this wrong?
App.js
import React from 'react';
import Todos from './components/Todos';
import Header from './components/Header';
class App extends React.Component {
state = {
searchTerm : '',
todos: [{
id: 1,
completed: true,
text: "I am number one"
},
{
id: 2,
completed: false,
text: "I am number two"
},
{
id: 3,
completed: false,
text: "I am number three"
}]
}
markComplete = (id) => {
this.setState({
todos: this.state.todos.map(todo => {
if (todo.id === id) {
todo.completed = !todo.completed;
}
return todo;
})
});
}
deleteTo = (id) => {
this.setState({
todos: [...this.state.todos.filter(todo => todo.id!==id)]
});
}
search = (evt) => {
const value = evt.target.value;
this.setState({
searchTerm: value
});
}
render() {
return (
<div>
<Header />
{ console.log(this.state.searchTerm) }
<input type="text" onChange = {this.search} />
<Todos todos = {this.state.todos} markComplete = {this.markComplete} deleteTo = {this.deleteTo}/>
</div>
);
}
}
export default App;
Todos.js
import React, { Component } from 'react';
import TodoItem from './TodoItem';
class Todos extends Component {
render() {
return this.props.todos.map((todo) =>
<TodoItem key={todo.id} todo = {todo} markComplete = {this.props.markComplete} deleteTo={this.props.deleteTo}/>)
}
}
export default Todos;
TodoItem.js
import React, { Component } from 'react';
class TodoItem extends Component {
getStyle = () => {
return { textDecoration: this.props.todo.completed ? "line-through": "none" };
};
getButtonStyle = () => {
return {
backgroundColor: 'red',
border: 'none',
cursor: 'pointer',
float: 'right',
padding: '5px 10px',
borderRadius: '50%'
};
}
render() {
const {id, text} = this.props.todo;
return (
<div style={this.getStyle()}>
<p>
<input type="checkbox" onChange= { () => this.props.markComplete(id) }/> {' '}
{text}
<button style={this.getButtonStyle()} onClick = { () => this.props.deleteTo(id)}> x </button>
</p>
</div>
);
}
}
export default TodoItem;
it's probably because of React StrictMode in your index.js file (if you use create-react-app).
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
serviceWorker.unregister();
strict mode checks for potential problems and caused to run some lifecycle methods twice (like constructor, render, componentShouldUpdate, etc).
Strict mode checks are run in development mode only; they do not impact the production build.
you can read more about it on strict-mode
The render function can be called almost any number of times before the commit phase occurs and updates are flushed to the DOM. The render function should also be a pure function, meaning there are no side-effects, like console logging. Instead use the componentDidUpdate lifecycle function to log when state or props update. Perhaps this diagram would help.
A simple solution to avoid the side effects is to not assign Dynamic values in the Render(), just keep it in the state, and call it before the Render() as example on Click() method.

Render happens before componentwillmount Meteor API call

Hello I'm having some trouble with async. It seems that the render is called before the api call is done in the componentwillmount
// Framework
import React, { PureComponent } from "react";
// Components
import Page from "../components/Page.jsx";
import Button from "../components/Button.jsx";
class Home extends PureComponent {
constructor(props) {
super(props);
this.state = {
order: null,
error: null
};
}
componentWillMount() {
Meteor.call("orders.getLastOrder", (error, response) => {
if (error) {
this.setState(() => ({ error: error }));
console.log(error);
} else {
this.setState(() => ({ order: response }));
console.log(this.state.order[0].name);
}
});
}
goBack = () => this.props.history.push("/shop");
goCart = () => this.props.history.push("/cart");
render() {
return (
<Page pageTitle="Cart" history goBack={this.goBack} goCart={this.goCart}>
<div className="home-page">
<div>
{this.state.order.map((item, i) => <div key={i}> {item.name}
{item.price} {item.quantity}</div>)}
</div>
<Button
onClick={() => {
this.props.history.push("/shop");
}}
>
Go shopping
</Button>
</div>
</Page>
);
}
}
export default Home;
I am having trouble trying to figure out how to loop through the objects from my state and display them in rows (I'm trying to create a cart)
0:{name: "TEMPOR Top", price: 875.5, quantitiy: 6}
1:{name: "CONSECTETUR Coat", price: 329.8, quantitiy: 3}
_id:"6RNZustHwbKjQDCYa"
You will still get the extra render but I assume you getting an error on the .map() function?
if you only change your constructor into this:
constructor(props) {
super(props);
this.state = {
order: [],
error: null
};
}
you won't get the error, because you can't use .map on a null object but you can use it on a empty array.

Resources