I hope I am capable to explain my question in a understandable way.
I am very new to React! And I am not familiar with how some things get done yet.
I have a GamePage component. On this page I am rendering a GameRound component.
When the game starts, a new "game round" starts. After the game round has finished, I want a second and then a third game round to start. This means I need to get kind of a "new" GameRound. The GamePage should remain.
While writing this, I got an idea of how this could be achieved: In my gameRoundFinished() event, I could reset the state of the GameRound. But is that already the most elegant and especially the Reactive way doing this?
Thanks in advance.
Some code as requested...
GamePage.js
export class GamePage extends React.Component {
constructor(props) {
super(props);
// Set game configuration and data
this.state = {
gameId: props.match.params.gameId,
settings: {
categories: ['Stadt', 'Land', 'Fluss'],
countdown: 5
},
activePlayers: {},
game: null
};
// Open socket
this.socket = openSocket('http://localhost:3000');
this.socket.emit('join', this.state.gameId);
this.socket.on('updatePlayers', players => {
this.state.activePlayers = players;
this.setState(this.state);
});
this.socket.on('startGame', () => {
this.state.game = {
rounds: []
};
this.state.game.rounds.push({
});
this.setState(this.state);
});
}
onClick = () => {
this.socket.emit('ready');
}
render() {
if (this.state.game) {
return (
<GameRound socket={this.socket} config={this.state}></GameRound>
);
} else {
return (
<div>Currently {this.state.activePlayers.length}/3
<Button onClick={this.onClick}>klick</Button></div>
);
}
}
}
GameRound.js
export class GameRound extends React.Component {
// too much code that is irrelevant for the question
render() {
return ...
}
}
A very simple way to "reset" a component (and re-run the constructor) is to use the key prop.
See https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#recommendation-fully-uncontrolled-component-with-a-key
In your case: be sure to provide a new key at every round change: something like:
<GameRound key={roundId} socket={this.socket} config={this.state}></GameRound>
Before other things it is necessary to clean the code:
To call functions together with synthetic events in the render method:
<Button onClick={this.onClickEvent()}>click</Button></div>
To use this.setState({ a: 123 }) for changing this.state object, but not to use this.state itself for changing the state.
Good example:
this.setState({ activePlayers: players });
Bad examples
this.setState(this.state);
this.state.game = { rounds: [] };
this.state.game.rounds.push({ ... });
You may want to use socket.on() function to change your state as follows:
socket.on(){
...
this.setState({ activePlayers: players });
}
<Button onClick={this.socket.on()}>click</Button></div>
One solution is to design GameRound so that it accepts props to determine its behavior. Then to "reset", you just pass the appropriate values for the props to set it to the new round.
Related
I'm trying to make some component which data output depends on some external API.
So I have this snippet:
class Parent extends Component {
constructor(props) {
super(props)
this.state = {
somethingFromAPI: ''
}
}
componentDidMount() {
/*
something on axios.get() which updates this.state.somethingFromAPI
which normally can have some time delay till executed
*/
}
render() {
return (
<Child value={this.state.somethingFromAPI} />
)
}
}
class Child extends Component {
constructor(props) {
super(props)
this.state = {
value: this.props.value || ''
}
}
handleChange(event) {
this.setState({
value: event.target.value
})
}
static getDerivedStateFromProps(props, state) {
// if difference
return {
value: props.value
}
}
render() {
return (
<div>
<input value={this.state.value} onChange={this.handleChange.bind(this)} />
</div>
)
}
}
ReactDOM.render(
<Parent />
document.getElementById('app')
);
Seems like this works fine, initializing component, and getting API data, after that, input value seems to be updated, which is what I expect.
Problem that hurts me a lot is if I type something inside input, that will call handleChange, but will also trigger this getDerivedStateFromProps and will replace newer inputed value with that "old" from API.
Is this good way of doing this, maybe I made mistake at start with understanding of how it should be done? Guide me in right direction.
I'm yet pretty new to React.
Generally, need to make form which I can use for new input, or updating existing data (like some posts, etc.), so I can load API data.
Best regards.
Did you consider using shouldComponentUpdate instead if using getDerivedStateFromProps
something like this may solve your problem:
shouldComponentUpdate(nextProps, nextState) {
const { value: nextPropsValue } = nextProps;
const { value: propsValue } = this.props;
const { value } = this.state;
if (nextPropsValue !== propsValue && nextPropsValue !== value) {
this.setState({
value: nextPropsValue
});
}
return value !== nextState.value;
}
Update the answer adding comparison with current props value
I think using getDerivedStateFromProps here may be unnecessary. If you want to prevent a render in certain cases, consider using shouldComponentUpdate https://reactjs.org/docs/react-component.html#shouldcomponentupdate. But it sounds like you basically just need to use your input change handler to keep the state of the input, which you're already doing.
You should also check this article out on why someone shouldn't use getDerivedStateFromProps. It's very informative.
There're 2 ways to define a React component.
First one is like below.
class SomeComponent extends React.Component {
constructor (props) {
super(props)
this.state = {
someState: false
}
this._handleOnChangeState = this._handleOnChangeState.bind(this)
}
_handleOnChangeState (e) {
this.setState({ someState: e.target.value })
}
....
}
Second one is like below.
class SomeComponent extends React.Component {
state = {
someState: false
}
_handleOnChangeState = (e) => {
this.setState({ someState: e.target.value })
}
....
}
These two codes are the same function, but I guess there's some different something like memory usage or etc.
Can someone make it clearly? Thanks in advance!
This is a new proposal (class fields) for ES which is in stage 3 right now. To run a code written in this way you need a transpiler like Babel and an appropriate plugin.
Before transpile:
class A {
static color = "red";
counter = 0;
handleClick = () => {
this.counter++;
}
}
After transpile (with stage 2 on Babel Repl):
class A {
constructor() {
this.counter = 0;
this.handleClick = () => {
this.counter++;
};
}
}
A.color = "red";
In addition to the official proposal 2ality blog post is a good source to see what are the details.
Here is a reddit post if you have time to read the discussion storm what is the reason behind this proposal :)
The arrow function here is a different story. You can use instance properties without constructor and mix your code with standard functions. But when you want to use something like that this won't work:
class App extends React.Component {
state = { bar: "baz"}
foo() { console.log(this.state.bar) };
render() {
return <div><button onClick={this.foo}>Click</button></div>;
}
}
We need to bind our function in somehow like:
return <div><button onClick={this.foo.bind(this)}>Click</button></div>
But, binding our function in a JSX prop is no so good since it will create our function in each render.
One way to do this nicely bind in our constructor:
constructor(props) {
super(props);
this.foo = this.foo.bind( this );
}
But, if I have to write a constructor what is the point? This is why you see arrow functions everywhere where we define the classes like your second example. No need to bind to function thanks to arrow functions. But it is not directly related to this new proposal I think.
The first one is the traditional approach and the second one is when you babel-transform-class-properties plugin.
In the second type babel does the same thing under the hood, therefore it is a matter of convenience.
I tried to push a value into a state array but I get an issue TypeError: Cannot read property 'state' of undefined at this.state.rows.push(a);
Know why? I am trying to push a new value into the array after i click a button.
App.js
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
class App extends Component {
constructor() {
super();
this.state = {
name: '',
rows: ['hello',<p>gfdfg</p>,'mello']
}
}
handle(e){
e.preventDefault();
var a = "h";
this.state.rows.push(a);
alert("hello");
}
render() {
return (
<div className="App">
Button<br/>
<input type="submit" id="black" onClick={this.handle}/><br/>
<p>{this.state.rows}</p>
</div>
);
}
}
export default App;
There are couple of things that are wrong here:
you should NEVER change the state directly:
This is a big No No:
this.state.rows.push(a);
instead you should do something like this:
this.setState({ rows : [...this.state.rows, a] })
or without ES6:
const newArray = this.state.rows.slice();
newArray.push(a);
this.setState({ rows: newArray })
You should always replace the state with a new one.
this in a react component is not what you think it is, in order to make it work you can do one of two things:
a. change your method to an arrow function:
handle = (e) => {
e.preventDefault();
var a = "h";
this.state.rows.push(a);
alert("hello");
}
b. bind this to the method:
constructor() {
super();
this.state = {
name: '',
rows: ['hello',<p>gfdfg</p>,'mello']
}
this.handle = this.handle.bind(this);
}
the method handle does not have access to the context of the class i.e this; consider writing it as a fat arrow function
// class definition
handle = () => {
e.preventDefault();
var a = "h";
this.state.rows.push(a);
alert("hello");
}
render() {
// render logic
}
Having said this, mutating the state is not a good idea, consider using setState if you want your component to re-render as a result of state change
handle = () => {
e.preventDefault();
let { rows } = this.state;
var a = "h";
rows.push(a);
this.setState({
rows,
});
}
You are doing wrong, you have to use setState() method to push the value in the array:
handle = (e) => {
e.preventDefault();
var a = "h";
let tempRows = [...this.state.rows];
tempRows.push(a)
this.setState({rows:tempRows})
alert("hello");
}
You have two problems.
Event handlers require 'this' to be bound: https://reactjs.org/docs/handling-events.html So following this, you must either write: this.handle = this.handle.bind(this) in your contructor, or change handler to arrow function, if your build process supports transpilation of class fields.
React component will only update if component props change, or component state changes. Which is done by comparing references. In your case, when you push to the array, you are mutating the state, so the new reference is never created, and component does not re-render. If you want to verify that, just put console.log(this.state.rows) below this.state.rows.push(a) and you'll see that the array has received the new value, but component does not represent it. You need to use this.setState to create a new reference for your state, like so: this.setState({ rows: [...this.state.rows, a] })
Another way of returning a new array from the current array with additional elements and then pushing to state is to use the concat method.
Example :
this.setState({ users: this.state.users.concat(<Additonal Items Here>)}
I've been attempting to fetch some data from a server and for some odd reason componentDidMount() is not firing as it should be. I added a console.log() statement inside of componentDidMount() to check if it was firing. I know the request to the server works as it should As I used it outside of react and it worked as it should.
class App extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
obj: {}
};
};
getAllStarShips () {
reachGraphQL('http://localhost:4000/', `{
allStarships(first: 7) {
edges {
node {
id
name
model
costInCredits
pilotConnection {
edges {
node {
...pilotFragment
}
}
}
}
}
}
}
fragment pilotFragment on Person {
name
homeworld { name }
}`, {}). then((data) => {
console.log('getALL:', JSON.stringify(data, null, 2))
this.setState({
obj: data
});
});
}
componentDidMount() {
console.log('Check to see if firing')
this.getAllStarShips();
}
render() {
console.log('state:',JSON.stringify(this.state.obj, null, 2));
return (
<div>
<h1>React-Reach!</h1>
<p>{this.state.obj.allStarships.edges[1].node.name}</p>
</div>
);
}
}
render(
<App></App>,
document.getElementById('app')
);
The issue here is that the render method is crashing, because the following line is generating an error
<p>{this.state.obj.allStarships.edges[1].node.name}</p>
Fix this to not use this.state.obj.allStarships.edges[1].node.name directly, unless you can guarantee that each receiver is defined.
Check your component's key
Another thing that will cause this to happen is if your component does not have a key. In React, the key property is used to determine whether a change is just new properties for a component or if the change is a new component.
React will only unmount the old component and mount a new one if the key changed. If you're seeing cases where componentDidMount() is not being called, make sure your component has a unique key.
With the key set, React will interpret them as different components and handle unmounting and mounting.
Example Without a Key:
<SomeComponent prop1={foo} />
Example with a Key
const key = foo.getUniqueId()
<SomeComponent key={key} prop1={foo} />
Also check that you don't have more than one componentDidMount if you have a component with a lot of code. It's a good idea to keep lifecycle methods near the top after the constructor.
I encountered this issue (componentDidMount() not being called) because my component was adding an attribute to the component state in the constructor, but not in the Component declaration. It caused a runtime failure.
Problem:
class Abc extends React.Component<props, {}> {
this.state = { newAttr: false }; ...
Fix:
class Abc extends React.Component<props, {newAttr: boolean}> {
this.state = { newAttr: false }; ...
I am new to React and I find it interesting. I have a button and clicking on that button will set some state inside that class as true and based on that the title of the button will change.
I want to change some other React component in some other class based on this state change. How can I do that ?
For example :
var Comp1 = React.createClass({
getInitialState: function() {
return { logged_in: false }
},
handleClick: function() {
this.setState({logged_in: !this.state.logged_in});
},
render: function() {
return <button onClick={this.handleClick}>
{this.state.logged_in ? "SIGN OUT" : "SIGN IN"}
</button>;
}
})
Here is my second component which is rendered separately.
var Comp2 = React.createClass({
render: function(){
return <div>
{Comp1.state.logged_in ? "You are in" : "You need a login"}
</div>;
}
})
This is one component. I want to use the state of this component in another component either by passing the stat (though I am not using both the component in composite format, so they are independent by nature) or based on change in the state I can define another sate in the second component.
You need to provide a shared object that can handle the state that these two objects represent. It is going to be easier to pick a standard model for doing this, like redux or flux, than to come up with your own. Learning one of these patterns is crucial to development in React. I personally recommend redux.
This is a very stripped down version of what Flux would look like. It is not intended to be a real solution, just to provide a glimpse of the pattern. If you just want to skip to the code, here is a codepen.
The shared object is typically referred to as a store. Stores hold state, and provide methods to subscribe to changes in that state. Mutating this state would usually by done by calling publishing an event with dispatcher that notifies the store, but for simplicity I have included a publish function on the store itself
let loginStore = {
isLoggedIn: false,
_subscribers: new Set(),
subscribeToLogin(callback) {
this._subscribers.add(callback);
},
unsubscribeToLogin(callback) {
this._subscribers.delete(callback);
},
publishLogin(newState) {
this.isLoggedIn = newState;
this._subscribers.forEach(s => s(this.isLoggedIn));
}
};
Once a pub/sub system is in place, the components will need to subcribe to it. The login button mutates this state, so it will also publish.
class LoginButton extends React.Component {
constructor(...args) {
super(...args);
this.state = {isLoggedIn: loginStore.isLoggedIn};
}
update = (isLoggedIn) => {
this.setState({isLoggedIn});
}
componentDidMount() {
loginStore.subscribeToLogin(this.update);
}
componentDidUnmount(){
loginStore.unsubscribeToLogin(this.update);
}
handleClick = () => {
loginStore.publishLogin(!this.state.isLoggedIn);
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.isLoggedIn ? "SIGN OUT" : "SIGN IN"}
</button>
);
}
}
class LoginHeader extends React.Component {
constructor(...args) {
super(...args);
this.state = {isLoggedIn: loginStore.isLoggedIn};
}
update = (isLoggedIn) => {
this.setState({isLoggedIn});
}
componentDidMount() {
loginStore.subscribeToLogin(this.update);
}
componentDidUnmount(){
loginStore.unsubscribeToLogin(this.update);
}
render() {
return (
<div>{this.state.isLoggedIn ? "You are in" : "You need a login"}</div>
);
}
}
You'll notice the second component does not refer to the state of the first component, but the state of the store. As you mentioned, since they do not have a reference to each other, and do not have a common parent, they cannot depend directly on each other for the loggedIn state.