Sandbox link : Link to This CodeSandbox.io
Please help me understand the sequence of execution in this code.
When an error is thrown in child component ( Display ), the getDerivedStateFromError() runs and sets hasError state to true. After that, as expected render() runs....
What I didn't understand is.... Why is the constructor being called again ? This is counter-intuitive... Please explain the reason for its calling...
What did React achieve by restarting everything from the constructor ? It should've simply displayed the fallback UI..But it ran the constructor again and only after facing the same error again did it show the fallback UI. Why did this happen ?
This is my App.js file
import React from "react";
export const Button = (props) => {
return (
<button className={props.className} onClick={props.onClick}>
{props.children}
</button>
);
};
function Display(props) {
throw new Error();
return <h1>Counter {props.i}</h1>;
}
class Counter extends React.Component {
constructor() {
super();
console.log("constr ran");
this.state = {
count: 0,
updated: false,
firstRender: false,
hasError: false,
};
this.incHandler = this.incHandler.bind(this);
}
static getDerivedStateFromError() {
console.log("getDerivedStateFromError Ran...");
return {
hasError: true,
};
}
incHandler() {
this.setState((prevState) => {
return {
count: prevState.count + 1,
updated: true,
};
});
console.log(this.state.count);
}
decHandler = () => {
this.setState((prevState) => {
return {
count: prevState.count - 1,
updated: true,
};
});
console.log(this.state.count);
};
render() {
console.log("render() ran... hasError: ", this.state.hasError);
if (this.state.hasError) {
return (
<div className="App">
<h1>Error happened!</h1>;
</div>
);
}
return (
<div className="App">
<Display i={this.state.count} />
<Button className="button" onClick={this.incHandler}>
Increment
</Button>
<Button className="button" onClick={this.decHandler}>
Decrement
</Button>
<Button
className="button"
onClick={() => {
throw new Error();
}}
>
This Will Throw Error
</Button>
</div>
);
}
}
export default Counter;
This is my index.js file
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
reportWebVitals();
Just wanted to add more to this....
Even when componentDidCatch() is used to catch errors in child components,
Behaviour of React is same...
Please see below image and code
In App.js I made this small modification. This is also causing re-running of constructor.... Why ?
// static getDerivedStateFromError() {
// console.log("getDerivedStateFromError Ran...");
// return {
// hasError: true,
// };
// }
componentDidCatch() {
console.log("componentDidCatch running");
this.setState({ hasError: true });
}
So, apparently, whenever React encounters an error it tries to start everything from constructor...(with the hope of eliminating it...) It sure looks like that....
Related
it seems as if this set of code is not updating my state, and I am not sure why! The api is 100% sending back TRUE (as seen from axios console.log). Thank you advanced for the help!
import React, { Component } from 'react';
import axios from 'axios';
export class Test extends Component {
state = {
reponse: false
}
componentDidMount () {
axios.get(`/api`)
.then(res => {
console.log(res.data.success);
this.setState({ response: res.data.success });
});
}
render() {
if (this.state.reponse) {
return (
<div>
<h1>Response Gathered!</h1>
</div>
)
} else {
return (
<div>
<button onClick={() => console.log(this.state.reponse)}>Check State</button>
</div>
)
}
}
}
export default Test;
Change,
state = {
reponse: false
}
To,
state = {
response: false
}
There is a typo in state declaration (reponse to response)..
And modified code would look like,
class Test extends React.Component {
state = {
response: false
};
componentDidMount() {
axios.get(`/api`)
.then(res => {
console.log(res.data.success);
this.setState({ response: res.data.success });
});
}
render() {
if (this.state.response) {
return (
<div>
<h1>Response Gathered!</h1>
</div>
);
} else {
return (
<div>
<button onClick={() => console.log(this.state.response)}>
Check State
</button>
</div>
);
}
}
}
export default Test;
Working Codesandbox example
define state in the constructor function.
constructor(props) {
super(props);
this.state = {response: false};
}
btw, there was a spelling error.
render() {
return (
{this.state.response ?
<h1>Some text</h1> :
(<div>
<button onClick={() => console.log(this.state.response)}>
Check State
</button>
</div>)
}
);
}
I do sorting on reactjs, I can’t understand how to redraw all child components so that only one selected remains active, I can update the current one, but the others do not change. Here is the code for an example. Can anyone help / explain how to do it right?
nodejs, webpack, last reactjs
App.js
import React, { Component } from "react";
import Parent from "./Parent";
class App extends Component {
render() {
return(
<Parent />
)
}
}
export default App;
Parent.js
import React, { Component } from "react";
import Child from "./Child";
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
popularity: {"sorting": "desc", "active": true},
rating: {"sorting": "desc", "active": false},
reviews_count: {"sorting": "desc", "active": false},
};
}
updateFilters = () => {
// ??
};
render() {
return (
<div>
<Child type="popularity" sorting={this.state.popularity.sorting} active={this.state.popularity.active} updateFilters={this.updateFilters} />
<Child type="rating" sorting={this.state.rating.sorting} active={this.state.rating.active} updateFilters={this.updateFilters} />
<Child type="reviews_count" sorting={this.state.reviews_count.sorting} active={this.state.reviews_count.active} updateFilters={this.updateFilters} />
</div>
)
}
}
export default Parent;
Child.js
import React, { Component } from "react";
class Child extends Component {
handleClick = () => {
this.props.updateFilters();
};
render() {
let activeStr = "";
if (this.props.active) {
activeStr = "active"
} else {
activeStr = "inactive";
}
return(
<div onClick={() => this.handleClick}>
{this.props.type} {activeStr} {this.props.sorting}
</div>
);
}
}
export default Child;
Assuming you are trying to set the active flag for a clicked Type to true and also set all the other types to false.
<div onClick={() => this.handleClick}> this isn't correct, as you aren't invoking the function. This could be corrected to:
<div onClick={() => this.handleClick()}>
Then you can update handleClick to pass the Type:
handleClick = () => {
this.props.updateFilters(this.props.type);
};
OR
You could ignore that handleClick and call the prop function:
<div onClick={() => this.props.updateFilters(this.props.type)}>
Once you have passed the Type back into the updateFilters, we can simply iterate over the previous State Properties, setting all Types' Active Flag to false. However, if the Key matches the Type which was clicked, we set it to true.
updateFilters = type => {
this.setState(prevState => {
return Object.keys(prevState).reduce(
(result, key) => ({
...result,
[key]: { ...prevState[key], active: key === type }
}),
{}
);
});
};
Your Child component could be heavily refactored into a Pure Functional Component, making it a lot simpler:
const Child = ({ type, active, updateFilters, sorting }) => (
<div onClick={() => updateFilters(type)}>
{type} {active ? "active" : "inactive"} {sorting}
</div>
);
Work solution:
https://codesandbox.io/s/4j83nry569
I want to hide some component based on some flag in react js.
I have an App component where I have Login and other components, I want to hide the other component until Login components this.state.success is false and on click of a button I am changing the sate, but it's not working, I am new to react,
My App Class compoenent -
import React, { Component } from "react";
import logo from "../../logo.svg";
// import Game from "../Game/Game";
import Table from "../Table/Table";
import Form from "../Table/Form";
import Clock from "../Clock/Clock";
import "./App.css";
import Login from "../Login/Login";
class App extends Component {
state = {
success: false
};
removeCharacter = index => {
const { characters } = this.state;
this.setState({
characters: characters.filter((character, i) => {
return i !== index;
})
});
};
handleSubmit = character => {
this.setState({ characters: [...this.state.characters, character] });
};
handleSuccess() {
this.setState({ success: true });
}
render() {
const { characters, success } = this.state;
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<span className="Span-inline">App</span>
<Clock time={new Date()} />
</header>
<Login success={success} handleSuccess={this.handleSuccess} />
{success && (
<div className="container">
<h1>React Tutorial</h1>
<p>Add a character with a name and a job to the table.</p>
<Table
characterData={characters}
removeCharacter={this.removeCharacter}
/>
<h3>Add New character</h3>
<Form handleSubmit={this.handleSubmit} />
</div>
)}
{/* <Game /> */}
</div>
);
}
}
export default App;
My Login component -
import React, { Component } from "react";
import Greeting from "./Greeting";
import LogoutButton from "./LogoutButton";
import LoginButton from "./LoginButton";
class Login extends Component {
constructor(props) {
super(props);
this.handleLoginClick = this.handleLoginClick.bind(this);
this.handleLogoutClick = this.handleLogoutClick.bind(this);
this.state = {
isLoggedIn: false,
name: "",
success: false
};
}
handleLoginClick() {
this.setState({ isLoggedIn: true });
this.setState({ success: true });
}
handleLogoutClick() {
this.setState({ isLoggedIn: false });
this.setState({ success: false });
}
onChange = e => {
this.setState({
name: e.target.value
});
};
render() {
const isLoggedIn = this.state.isLoggedIn;
const name = this.state.name;
// const successLogin = this.state.success;
let button;
if (isLoggedIn) {
button = <LogoutButton onClick={this.handleLogoutClick} />;
} else {
button = <LoginButton onClick={this.handleLoginClick} />;
}
return (
<div>
<Greeting
isLoggedIn={isLoggedIn}
name={name}
onChange={this.onChange}
/>
{button}
</div>
);
}
}
export default Login;
please guide me on what I am doing wrong.
Why sometime debuggers do not trigger in react component?
For the sake of example I have used functional stateless component here. You can use Class component all upto you.
const conditionalComponent = (props) => {
let condition = true;
return (
{condition && <div><h1>Hello world</h1></div>}
}
Instead of directly giving condition you can even call function which returns a boolean value.
handleLoginClick() {
this.setState({ isLoggedIn: true });
this.setState({ success: true });
this.props.handleSuccess()
}
do like this
<Login success={success} handleSuccess=
{this.handleSuccess} />
bind this function
I am quite new to React and in this circumstance I feel like I should be using a lifecycle hook, but I'm not sure which. In the code below I am making a call to the API and retrieving the list of users object which contains another list of things they own called members. Like so:
When clicking the button 'Unlink' I am then storing the reference in state and posting that back up to the API to unlink the member:
import React, { Fragment } from 'react';
import FormScene from 'nwiadmin/scenes/form';
import Button from 'nwiadmin/components/button';
import { formatDate } from 'nwiadmin/utility/formatters';
import Modal from 'nwiadmin/components/modal';
import ModalActions from 'nwiadmin/components/modal/modalactions';
import SingleBusinesses from 'app/components/businesses';
let businessRef = '';
class UserBusinessSingleScene extends FormScene {
/* eslint-disable class-methods-use-this */
setInitialState(props) {
const pendingData = {
...props.data,
};
const reference = businessRef;
const isVisible = false;
this.setReferenceTarget = this.setReferenceTarget.bind(this);
return {
pendingData,
reference,
isVisible,
};
}
shouldComponentUpdate(nextProps, nextState) {
console.log('should cmp update', nextProps, nextState);
return true;
}
UNSAFE_componentWillUpdate(nextProps, nextState) {
console.log('cmp will update', nextProps, nextState);
}
componentDidUpdate(prevProps, prevState) {
console.log('cmp did update', prevProps, prevState);
}
setLabel(data) {
return data.name;
}
setMethod() {
return 'post';
}
setRemote() {
return `businesses/unclaim?reference=${this.state.reference}`;
}
modalToggleHander() {
this.setState(prevState => ({
isVisible: !prevState.isVisible,
}));
}
setReferenceTarget() {
this.setState({ reference: businessRef });
}
render() {
console.log(this.state.reference);
return (
<Fragment>
{this.state.pendingData.members.map(business => (
<SingleBusinesses
key={business.reference}
name={business.name}
reference={business.reference}
claimed={`Claimed on: ${formatDate(business.created_at)}`}
unlink={
<Button
onClick={() => {
businessRef = business.reference;
this.setReferenceTarget();
this.modalToggleHander();
}}
>
Unlink User
</Button>
}
/>
))}
<Modal isOpen={this.state.isVisible} title="Are you sure you want to unlink the user?">
<ModalActions
submit={() => this.submit()}
submitText="Unlink User"
cancel={() => this.modalToggleHander()}
/>
</Modal>
</Fragment>
);
}
}
export default UserBusinessSingleScene;
Once unlinked I get a Uncaught TypeError: Cannot read property 'map' of undefined error. I'm not 100% sure why but I feel like it's something to do with the state and that I should be setting the state after unlinking? Any help is appreciated.
I'm using Jest to test my React component but I've encountered an error I had never seen before.
here's my <Row/> component:
class Row extends React.Component {
constructor() {
super();
this.state = { userId: '', showDetails: false, showModal: false, status: '', value: '' }
this.handleStatusChange = this.handleStatusChange.bind(this)
this.handleModalCancel = this.handleModalCancel.bind(this)
this.handleModalConfirm = this.handleModalConfirm.bind(this)
this.showDetailsPanel = this.showDetailsPanel.bind(this)
}
showDetailsPanel() {
if(!this.state.showDetails) {
this.setState({
showDetails: true
});
} else {
this.setState({
showDetails: false
});
}
}
handleModalCancel() {
this.setState({
showModal: false
});
}
handleStatusChange(e) {
this.setState({
showModal: true,
value: e.target.value,
});
}
handleModalConfirm() {
this.setState({
showModal: false
});
this.props.model.status = this.state.value;
}
componentWillMount() {
this.props.model.fullName = `${this.props.model.firstName} ${this.props.model.lastName}`
}
render() {
const { model, modelProps, actions } = this.props;
return (
<div className="c-table__row">
{this.state.showModal ?
<Modal
title="Are you sure?"
copy={`Are you sure that you want to change the staus of this user to ${this.state.value}?`}
confirmText="Yes I'm sure"
cancel={this.handleModalCancel}
submit={this.handleModalConfirm}
/>
: null
}
<div className="c-table__row-wrapper">
{modelProps.map((p, i) =>
<div className={`c-table__item ${model[p] === "Active" || model[p] === "Suspended" || model[p] === "Locked" ? model[p] : ""}`} key={i}>
{model[p]}
</div>
)}
<div className="c-table__item c-table__item-sm">
<a onClick={this.showDetailsPanel} className={this.state.showDetails ? "info showing" : "info"}><Icon yicon="Expand_Cross_30_by_30" /></a>
</div>
</div>
{this.state.showDetails ?
<Details
tel={model.telephoneNumber}
dept={model.department}
role={model.role}
username={model.username}
status={model.status}
statusToggle={this.handleStatusChange}
/>
: null }
</div>
);
}
}
export default Row;
And here's my test:
import React from 'react';
import {shallow, mount} from 'enzyme';
import { shallowToJson } from 'enzyme-to-json'
import renderer from 'react-test-renderer';
import Row from '../../../src/components/tables/row.jsx';
test('clicking the info button should call showDetailsPanel', () => {
const component = mount(<Row model={{a: 1, b: 2}} modelProps={['a', 'b']}/>)
const showDetailsPanel = jest.spyOn(component.instance(), 'showDetailsPanel');
component.update();
const button = component.find('.info')
button.simulate('click')
expect(showDetailsPanel).toBeCalled();
})
So I'm simply trying to check that when the info button is clicked the showDetailsPanel is called, but it fails with TypeError: jest.spyOn is not a function.
I'm using "jest": "^18.1.0","jest-cli": "^18.1.0","jest-junit": "^1.5.1".
Any idea?
Thanks in advance
You're using Jest version 18.1.0 but jest.spyOn has been added in 19.0.0.
If you want to use it, you need to upgrade Jest.
npm install --save-dev jest#latest
Or if you're using Yarn:
yarn upgrade jest --latest