React Router v4 previous state component - javascript

I'm looking for a solution - react router v4 doesn't hold the previous state of component. For example, here is a simple counter:
import React, { Component } from "react";
class Schedule extends Component {
constructor(props) {
super(props);
this.state = {
counter: 0
};
this.holdState = this.holdState.bind(this);
}
holdState() {
this.props.location.state = this.state.counter;
const state = this.state;
this.setState({
counter: state.counter + 1
});
}
render() {
return (
<div>
<ul>
<li>6/5 # Evergreens</li>
<li>6/8 vs Kickers</li>
<li>6/14 # United</li>
</ul>
<div>
<button onClick={() => this.holdState()}>Click</button>
<span>{this.state.counter}</span>
</div>
</div>
);
}
}
export default Schedule;
I was trying to push the state into location and history by props.
But whenever I press "browser button back" from the next page, it always resets the state.
Can someone tell me where I'm doing a mistake?

Whenever your component mounts, your constructor will be initiated which will reset the state back to 0. This happens when you are on another component and press back button in which case your current Route gets mounted.
Also directly mutating the location.state value is not a right approach. What you need is to save your value in localStorage and refill it in state if it exists.
Another thing is that when you wish to update the state based on prevState, you could make use of functional setState. Check this for more details:
When to use functional setState
import React, { Component } from "react";
class Schedule extends Component {
constructor(props) {
super(props);
this.state = {
counter: localStorage.getItem('counter') || 0
};
this.holdState = this.holdState.bind(this);
}
holdState() {
localStorage.setItem('counter', this.state.counter);
this.setState(prevState => ({
counter: prevState.counter + 1
}));
}
render() {
return (
<div>
<ul>
<li>6/5 # Evergreens</li>
<li>6/8 vs Kickers</li>
<li>6/14 # United</li>
</ul>
<div>
<button onClick={() => this.holdState()}>Click</button>
<span>{this.state.counter}</span>
</div>
</div>
);
}
}
export default Schedule;

I was trying to push state into location and history by props. BBut whenever I > press "browser button back" from the next page, it always resets the state.
when you call setState you're not changing the route, you're just triggering a rerender of your component.
If you press the back button after incremented the counter react router will symply pop the last route from the history stack but since you don't have a previous route the component will be remount hence the resetted state.
To implement what i suppose you want to achieve you need to explicitely change the route every setstate (e.g. adding a parameter in the query string with the current value of the counter, like ?counter=1, ?counter=2..) , this way you'll be sure that a new route will be push on top of the stack every setState and the back button will then work as you expect.

Related

How to update the state in mounting in reactjs

how to update the state after mounting or how to pass the state when moving from one page to another in react.
In My Scenario, by clicking button Add New, it directs to AddNew page, in that clicking on save will redirect to display page, that works.
when i move to addnew page
In Display multiselect remain same(always en), how to get rendered state after redirecting
class Display extends React.PureComponent{
constructor(props) {
super(props);
}
componentWillMount() {
console.log(this.state.language);
const defLanguage = this.props.loginData.language; // "en"
this.setState({ language: defLanguage.split(",") }, () =>
this.callQueryApiForFetch("ONLOAD")
);
}
render(){
<MultiSelect
filterOptions={formatLanguageArray(
languageConfig.pvalues
)}
setSelectedFieldValues={value => {
this.setState({ language: value }, () => {
this.callQueryApiForFetch("ONLOAD");
});
}}
id="language"
itemsSelected={this.state.language}
label="Select Language"
/>
<button className="page-header-btn icon_btn display-inline">
<img
title="edit"
className="tableImage"
src={`${process.env.PUBLIC_URL}/assets/icons/ic_addstore.svg`}
/>
{`Add New`}
</button>
}
}
class AddNew extends React.Component {
constructor(props) {
super(props);
}
componentWillReceiveProps = () => {
const defLanguage = this.props.location.state.language;
this.setState({ language: defLanguage });
}
render(){
<Link
to={{
pathname: "/ui/product-info",
state: {
language: this.state.language
}
}}
className="btn btn-themes btn-rounded btn-sec link-sec-btn"
>
Save
</Link>
}
Since you mentioned to redirect to next page , you can also try this.props.history.push("/report") here "/report" is the link you want to redirect to.
You can check React-Routing Concept (just a suggestion)
For sending props from parent component to child component
your case :
componentWillReceiveProps(nextProps){
if(nextProps.someValue!==this.props.someValue){
//Perform some operation
this.setState({someState: someValue });
this.classMethod();
}
}
It is a good practice in React to separate state management from component rendering logic. The idea is that you pass fragments of a centralized state to your component hierarchy, from top to bottom, and allow them to use these data in their render method. In this approach you avoid keeping application state within the components.
Whenever a component needs to change the application state, it dispatches a message that is processed by a function called "reducer", and updates the state in the store. The whole concept is based on having a single source of truth for the entire application, and preventing components from manipulating the store directly.
The standard way to implement this is to use a design pattern called Redux, which is made available to React apps through the react-redux library.
I suggest you take a look at this tutorial to learn how you use the React Redux library in practice.
1.make the Display as Component and not PureComponent.
2.define a local state in Display
// default state..
state = {
language: 'us',
}
3.console whether your'e getting language in Display's props(will mount).
//probably inside
this.props.location.state
4.then set the state
this.setState(prevState => ({
...prevState,
language: --value--,
})

undefined in variable using react componentDidMount

I have no clue why this.role is undefined in render.
export default class Dashboard extends Component {
componentDidMount() {
this.role = window.localStorage.getItem('role')
console.log('role', this.role) //return admin
}
render(){
console.log('role', this.role) //return undefined
return(
<div>
Component
</div>
)
}
}
I checked the localStorage of my app and it has value.
what happens is that at the initial render, render() method is called (before componentDidMount() is called), so it shows 'undefined'.
changing the value of 'this.role' won't re-render the page.
You will have to use state for this.
Below code should work I believe.
export default class Dashboard extends Component {
constructor(){
super()
this.state = {
role : undefined
}
}
componentDidMount() {
this.setState({role: window.localStorage.getItem('role')})
console.log('role', this.role) //return admin
}
render(){
console.log('role', this.state.role) //return undefined
return(
<div>
Component
</div>
)
}
}
It's returning undefined because you're setting this.role after the component is mount (componentDidMount). So the first render doesn't have this.role.
After componentDidMount is run you're not changing the state and the render is not running again (and therefore not getting the new info).
Try with componentWillMount instead, it should probably work.
Here's the React Lifecycle documentation.
Edit: Added code.
export default class Dashboard extends Component {
componentWillMount() {
this.role = window.localStorage.getItem('role')
console.log('role', this.role) // return admin
}
render(){
console.log('role', this.role) // now returning admin because this.role is set before the 1st render
return(
<div>
Component
</div>
)
}
}
As other users have pointed out, you can also use setState instead and it would also work (In that case, when the state changes the render is run again and your role is displayed accordingly).
You see undefined in the view because by the time the component has rendered there was nothing in role because componentDidMount is called after the initial render. Moreover, the component doesn't rerender after you have set role value from localStorage because it is not on the state. If you place role on the state and do this:
componentDidMount() {
this.setState({ role: window.localStorage.getItem('role')});
}
render(){
console.log('role', this.state.role)
return(
<div>
Component
</div>
)
}
then you will be able to see value of role in the view, but it will cause extra rerender of the component since you will change its state, according to react docs about componentDidMount:
Calling setState() in this method will trigger an extra rendering, but
it will happen before the browser updates the screen.
You can read more about componentDidMount here.
Update:
In your case you don't have to put role on the state, but then you can fetch its value from the constructor:
constructor(props) {
super(props);
this.role = window.localStorage.getItem('role');
}
and it will be available in the view.

Timer does not countdown in ReactJS

I am building a simple stopwatch app in ReactJS (count down to 0).
So far I have an input where the user can set how long the timer should be and a button which updates the timer state with the input data.
However I am not able to have the timer countdown, even with using setInterval().
App.jsx
import React, {Component} from 'react';
import Timer from './timer';
import './app.css';
import { Form, FormControl, Button} from 'react-bootstrap';
export class App extends Component {
constructor(props) {
super(props);
this.state = {
timerPosition: '',
newTimerPosition: '',
};
}
startTimer() {
this.setState({timerPosition:this.state.newTimerPosition})
}
render() {
return (
<div className="App-title"> Stopwatch </div>
<Timer timerPosition={this.state.timerPosition} />
<Form inline>
<FormControl
className="Deadline-input"
onChange={event => this.setState({newTimerPosition: event.target.value})}
placeholder='Set timer'/>
<Button onClick={() => this.startTimer()}>Set new timer</Button>
</Form>
</div>
)}
}
export default App;
Timer.jsx
import React, {Component} from 'react';
import './app.css';
export class Timer extends Component {
constructor(props) {
super(props);
this.state = {
secondsRemaining: ''
}
}
componentWillMount(){
this.startTimer(this.props.timerPosition);
}
componentDidMount(){
setInterval(() => this.startTimer(this.props.timerPosition), 1000);
}
startTimer(timerCallback) {
this.setState({secondsRemaining: timerCallback --})
}
render() {
return (
<div>
<div></div>
{this.state.secondsRemaining} seconds remaining
</div>
)}
}
export default Timer;
The timer does not decrement every second and just stays at the original position.
The primary issue is in the interval callback in Timer's componentDidMount:
componentDidMount(){
setInterval(() => this.startTimer(this.props.timerPosition), 1000);
}
Note that you're constantly reusing this.props.timerPosition as the timer position, instead of using the current state. So you're setting the state back to the initial state from the props.
Instead, you want to use the current state. How you use that state will depend on how you want the timer to behave, but beware of two things:
Never call this.setState({foo: this.state.foo - 1}) or similar. State updates are asynchronous and can be combined. Instead, pass a callback to setState.
Nothing is guaranteed to happen at any particular time or on a specific interval, so don't just decrement your counter; instead, see how long it's been since you started, and use that information to subtract from your initial counter value. That way, if your timer is delayed, you still show the correct value.
The problem is you keep using the same prop value secondsRemaining. You decrement the same value in the propthis.props.timerPosition each time. So the state value secondsRemaining never changes.
Instead decrement the secondsRemaining value from state using the setState method which takes a callback. The first parameter of the callback is the current state. so you do:
this.setState((prevState, props) => {
return { secondsRemaining: prevState.secondsRemaining - 1 };
});
You also need to set your initial state value to the supplied props value:
componentWillMount(){
this.setState({ secondsRemaining: this.props.timerPosition });
... other code
}

Trigger a function when action is dispatched from other component

I am working on a reactjs application and using redux. I am having two components. Say Component A and Component B. In component A I have a table. When I click any row, I dispatcch an action with row's data as parameters of that action. Every time I click a row, this action is dispatched. I want to trigger a function in Component B whenever a row is clicked and that action is dispatched. How can I do it?
Normally we change the data in reducer via action dispatch and then use that data as state in other components. But here i want to trigger a function in Component B whenever a row is clicked in the table of component A.
Dispatch an action in A when row clicked, that sets a boolean value to true. Then use mapStateToProps to re-render the component on Redux state change. Then make a conditional call to the required function inside componentDidUpdate():
class B extends React.Component {
componentDidUpdate() {
const { rowClickedInAComponent } = this.props;
if ( rowClickedInAComponent ) {
functionToBeCalled();
}
}
render() {
}
}
const mapStateToProps = (state) => ({
rowClickedInAComponent: rowClickedInAComponent
// boolean here, you could pass any info such as which row was clicked
});
export default connect(mapStateToProps)(ListingPage);
Comment if you need further clarification.
You can achieve this through couple of ways some are the following.
i) If there is parent child relation just pass activeRow function as a props of child component
class ComponentA extends React.component{
render(){
return(
{/*view, could be table, section, div, etc.*/}
<div>
<button onClick={this.props.handler}>Click me</button>
</div>
)
}
}
class ComponentB extends React.component{
handleClick(){
//do something.
console.log("clicked!");
}
render(){
return(
{/*view, could be table, section, div, etc.*/}
<div>
<ComponentA handler={this.handleClick}/>
</div>
)
}
}
ii)Using redux, create an activeRow key in redux state and subscribe componentB using react-redux's connect method.
Now in componentB add componentWillReceiveProps life cycle hook, and do something in inside componentWillReceiveProps
Ex:
componentWillReceiveProps(nextProps){
//first parameter is updated props.
//check this.props with nextProps.
//do something here.
}

Uncaught RangeError Maximum call stack size exceeded in React App

I'm learning React and for training, I want to create a basic Todo app. For the first step, I want to create a component called AddTodo that renders an input field and a button and every time I enter something in the input field and press the button, I want to pass the value of the input field to another component called TodoList and append it to the list.
The problem is when I launch the app, the AddTodo component renders successfully but when I enter something and press the button, the app stops responding for 2 seconds and after that, I get this: Uncaught RangeError: Maximum call stack size exceeded and nothing happens.
My app source code: Main.jsx
import React, {Component} from 'react';
import TodoList from 'TodoList';
import AddTodo from 'AddTodo';
class Main extends Component {
constructor(props) {
super(props);
this.setNewTodo = this.setNewTodo.bind(this);
this.state = {
newTodo: ''
};
}
setNewTodo(todo) {
this.setState({
newTodo: todo
});
}
render() {
var {newTodo} = this.state;
return (
<div>
<TodoList addToList={newTodo} />
<AddTodo setTodo={this.setNewTodo}/>
</div>
);
}
}
export default Main;
AddTodo.jsx
import React, {Component} from 'react';
class AddTodo extends Component {
constructor(props) {
super(props);
this.handleNewTodo = this.handleNewTodo.bind(this);
}
handleNewTodo() {
var todo = this.refs.todo.value;
this.refs.todo.value = '';
if (todo) {
this.props.setTodo(todo);
}
}
render() {
return (
<div>
<input type="text" ref="todo" />
<button onClick={this.handleNewTodo}>Add to Todo List</button>
</div>
);
}
}
AddTodo.propTypes = {
setTodo: React.PropTypes.func.isRequired
};
export default AddTodo;
TodoList.jsx
import React, {Component} from 'react';
class TodoList extends Component {
constructor(props) {
super(props);
this.renderItems = this.renderItems.bind(this);
this.state = {
todos: []
};
}
componentDidUpdate() {
var newTodo = this.props.addToList;
var todos = this.state.todos;
todos = todos.concat(newTodo);
this.setState({
todos: todos
});
}
renderItems() {
var todos = this.state.todos;
todos.map((item) => {
<h4>{item}</h4>
});
}
render() {
return (
<div>
{this.renderItems()}
</div>
);
}
}
export default TodoList;
First time componentDidUpdate is called (which happens after first change in its props/state, which in your case happens after adding first todo) it adds this.props.addToList to this.state.todo and updates state. Updating state will run componentDidUpdate again and it adds the value of this.props.addToList to 'this.state.todo` again and it goes infinitely.
You can fix it with some dirty hacks but your approach is a bad approach overall. Right thing to do is to keep todos in parent component (Main), append the new todo to it in setNewTodo (you may probably rename it to addTodo) and pass the todos list from Main state to TodoList: <TodoList todos={this.state.todos}/> for example.
The basic idea of react is whenever you call setState function, react component get updated which causes the function componentDidUpdate to be called again when the component is updated.
Now problem here is you are calling setState function inside componentDidUpdate which causes the component to update again and this chain goes on forever. And every time componentDidUpdate is called it concat a value to the todo. So a time come when the memory gets full and it throws an error. You should not call setState function inside functions like componentWillUpdate,componentDidUpdate etc.
One solution can be to use componentWillReceiveProps instead of componentDidUpdate function like this:
componentDidUpdate(nextProps) {
var newTodo = nextProps.addToList;
this.setState(prevState => ({
todos: prevState.todos.concat(newTodo)
}));
}

Categories

Resources