I have a React app that gets initialized as simple as:
let globalTodos = some_fetch_from_localstorage();
...
function changeGlobalTodos() {
globalTodos = another_fetch_from_localstorage();
}
...
ReactDOM.render(<ReactApp todos={globalTodos} />, document.getElementById('app'));
Inside of the app I'm doing the following:
constructor(props) {
super(props);
this.state = {
todos: []
};
}
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.todos !== prevState.todos) {
return { todos: nextProps.todos };
} else return null;
}
componentDidUpdate(prevProps, prevState) {
if (prevProps.todos !== this.props.todos) {
this.setState({ todos: this.props.todos });
}
}
The problem is that whenever I update globalTodos, the props on the React app don't get updated: it stays on the initial globalTodos's value.
I have tried playing with getDerivedStateFromProps is being called only on first setup of the props while componentDidUpdate never gets called :-/
What am I missing here?
I can't leave a comment, so I'll just post this here. React won't re-render unless you're updating a state.
I'd make globalTodos a state and add onto it from there using setState, then you can pass that on as a prop to the child component in your case ReactApp. You don't need to change them as states in your child component.
Example:
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
globalTodos: initialFetchedArray
};
}
changeGlobalTodos() {
let newTodos = fetchNewArray;
this.setState({globalTodos: newTodos});
}
ReactDOM.render(<ReactApp todos={globalTodos} />, document.getElementById('app'));
}
//You just need your prop here, here you can do whatever you want to do with the array if it's display you can use map
class Child extends Component {
render {
return(
{this.props.todos}
)
}
}
Really the main thing here is making your globalTodos a state, using setState to change that state, and just passing that state down as a prop.
Related
This is my parent code:
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
tags: [],
};
}
componentDidMount() {
this.getTags();
}
getTags() {
//method gets tags from the backend
}
render() {
return <Child tags={this.state.tags} />;
}
}
And this is basically my child component:
export default class Child extends Component {
constructor(props) {
super(props);
this.state = {
tags: props.tags,
};
}
componentWillReceiveProps(nextProps) {
this.setState({
tags: nextProps.tags,
});
}
}
But when I console log tags somewhere in the Child component, it is undefined. Maybe it is undefined because the child component gets rendered before the parent component calls the method getTags? Or is there any other problem with this code? And how can I avoid this problem that tags are undefined in the child component?
Cheers
To avoid your problem, you shouldn't be rendering your Child component until the this.state.tags has any useful values.
Here is how you can do it and also show a "Loading..." text, so the user isn't worried the page is broken.
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
tags: [],
};
}
componentDidMount() {
this.getTags();
}
getTags() {
//method gets tags from the backend
}
render() {
return this.state.tags.length ? (
'Loading...'
) : (
<Child tags={this.state.tags} />
);
}
}
Your child component will definitely get rendered with the empty 'tags' array as a prop. Then, when getTags() returns the data, the newly populated tags array will be passed to the child as a prop, forcing the child to get re-rendered with the new data.
It should be the empty array though, not "undefined". You might check your getTags() method and the API you are calling to make sure you aren't getting "undefined" from there.
componentWillReceiveProps is legacy and should not be used. See the following link in the React docs for details: https://reactjs.org/docs/react-component.html#unsafe_componentwillreceiveprops
That documentation will walk you through what to do if you need to perform side effects as a result of changing props.
Right now the only thing is componentWillReceiveProps is to set local state to the props, which is totally superfluous. Is there something else you are needing to do there?
I read on the internet that it is a bad bad coding practice to perform the following:
class Test extends React.Component {
constructor(props) {
super(props);
this.state = {
someItem = props.someItem
};
}
}
However, if I had this scenario instead:
class Test extends React.Component {
constructor(props) {
super(props);
this.state = {
someItem = this.props.someItem
};
}
}
Test.propTypes = {
someItem: PropTypes.array
};
function mapStateToProps(state, ownProps) {
[...]
return {
someItem: someItem
};
}
export default withRouter(connect(mapStateToProps)(Test));
Will there be an issue? Cannot find anywhere on the net that does not say that i cannot do that.
I am trying to ensure that everytime i navigate back to this component, the component is able to get the data from the Redux Store. I tried using componentWillReceiveProps(), but so far it only runs once and componentDidMount and componentWillMount does not accept setState.
Cheers.
You should not save props in state unless and until you would want to modify it at a later point in time locally and update it after some action to the provider of props. In short you state must not be directly derivable from props at all points of time in your code.
Even though you use redux mapStateToProps to provide props to component, its still the same as coming from parent
class Test extends React.Component {
render() {
console.log(this.props.someItem);
}
}
Test.propTypes = {
someItem: PropTypes.array
};
function mapStateToProps(state, ownProps) {
[...]
return {
someItem: someItem
};
}
export default withRouter(connect(mapStateToProps)(Test));
I am trying to do a simple thing:
check if a parameter is on the URL and then set a state for my component. This state will determine if some html code should be displayed or not.
Basically this dummy sample may give you an idea of what I have:
class Lalala extends Component {
constructor(props) {
super(props);
this.state = {showStatement : false}
}
parseURLParams(urlParams) {
if (urlParams.indexOf('blabla')> -1) {
this.setState({
showStatement: true
})
}
}
render() {
const { location } = this.prop
this.parseURLParams(location.search);
}
}
So, as you can see, every time it renders, it calls the parseURLParams function which tries to set the state, and, of course, when the setState is called, the render function is being called again, causing a infinite loop which ends up returning this error in the console.
Could you guys tell me a better way to set this state? once this is something that doesn't depend on an event, I am a bit confused.
Thanks
cause you using setState in render. It willbe render -> setState -> render-> setState -> render... You should move this.parseURLParams(location.search); to other lifecycle like this
componentWillReceiveProps(nextProps) {
if(JSON.stringify(nextProps.location) !== JSON.stringify(this.props.location)){
this.parseURLParams(this.props.location.search);
}
}
Try setting state in a different lifecycle hook, like componentDidUpdate. Also, if the prop value you want is available at the initial render, you'll want to set it in state there as well:
class Lalala extends Component {
constructor(props) {
super(props);
this.state = {
showStatement : false,
showBrokerStatements: props.location && props.location.search && props.location.search.indexOf('blabla')> -1
}
}
componentDidUpdate() {
this.parseURLParams(this.props.location.search);
}
parseURLParams(urlParams) {
if (urlParams.indexOf('blabla')> -1) {
this.setState({
showBrokerStatements: true
})
}
}
render() {
const { location } = this.prop
}
}
Given the following component structure I wan't to update my state in Parent and this should then update my GrandChild. I know I can make it work making Child stateless or only update State in Child but in our application this is the current structure. Is there any good way to do this or should we re-design? We keep a state in parent since we do not wan't to issue to many http requests in the application. In parent we fetch data that we need and make initial modifications. This data in then sent down to other components that in turn have child components, hence the example structure.
What gets printed in this example: Test: 1235
import * as React from 'react';
import * as ReactDOM from 'react-dom';
interface IProps {
}
interface IState {
cases: number[];
}
class Parent extends React.Component<IProps, IState> {
constructor(props: IProps) {
super(props);
this.state = {
cases: [1, 2, 3],
};
}
componentDidMount() {
let newCase = 4;
let newCases = Array.from(this.state.cases);
newCases.push(newCase)
this.setState({
cases: newCases
});
}
render() {
return (
<div>
<Child cases={this.state.cases} />
</div>
);
}
}
interface IChildProps {
cases: number[];
}
interface IChildState {
cases: number[];
}
class Child extends React.Component<IChildProps, IChildState> {
constructor(props: IChildProps) {
super(props);
this.state = {
cases: this.props.cases,
};
}
componentDidMount() {
let newCase = 5;
let newCases = Array.from(this.state.cases);
newCases.push(newCase)
this.setState({
cases: newCases
});
}
render() {
return (
<GrandChild cases={this.state.cases} />
);
}
}
interface IGrandChildProps {
cases: number[];
}
interface IGrandChildState {
}
class GrandChild extends React.Component<IGrandChildProps, IGrandChildState> {
constructor(props: IGrandChildProps) {
super(props);
}
render() {
return (
<div>
Test: {this.props.cases.map((value, index) => {
return <span key={index}>{value}</span>
}
)}
</div>
);
}
}
export default Parent
The problem here is that you are mapping props to state, once that happens you are the one who is in charge of updating the state when props change. Since you only use componentDidMount it only maps the props to state once. I typically lean towards avoiding having a component convert props into its own state and instead just have the parent pass down the props to the child in whatever way it needs it and not have to worry about changing the state when props change in the child component.
The other option is to use the componentWillReceiveProps lifecycle method and do the following
setCases(cases) {
let newCase = 5;
let newCases = Array.from(cases);
newCases.push(newCase)
this.setState({
cases: newCases
});
}
componentDidMount() {
this.setCases(this.props.cases)
}
componentWillReceiveProps(nextProps) {
this.setCases(nextProps.cases);
}
the componentDidMount will handle setting the state on initial loading and then componentWillReceiveProps will handle changing the state whenever the props change.
I m actually learning reactjs and I m actually developping a little TODO list, wrapped inside of a "parent component" called TODO.
Inside of this parent, I want to get the current state of the TODO from the concerned store, and then pass this state to child component as property.
The problem is that I dont know where to initialize my parent state values.
In fact, I m using ES6 syntax, and so, I dont have getInitialState() function. It's written in the documentation that I should use component constructor to initialize these state values.
The fact is that if I want to initialize the state inside of my constructor, the this.context (Fluxible Context) is undefined actually.
I decided to move the initialization inside of componentDidMount, but it seems to be an anti pattern, and I need another solution. Can you help me ?
Here's my actual code :
import React from 'react';
import TodoTable from './TodoTable';
import ListStore from '../stores/ListStore';
class Todo extends React.Component {
constructor(props){
super(props);
this.state = {listItem:[]};
this._onStoreChange = this._onStoreChange.bind(this);
}
static contextTypes = {
executeAction: React.PropTypes.func.isRequired,
getStore: React.PropTypes.func.isRequired
};
componentDidMount() {
this.setState(this.getStoreState()); // this is what I need to move inside of the constructor
this.context.getStore(ListStore).addChangeListener(this._onStoreChange);
}
componentWillUnmount() {
this.context.getStore(ListStore).removeChangeListener(this._onStoreChange);
}
_onStoreChange () {
this.setState(this.getStoreState());
}
getStoreState() {
return {
listItem: this.context.getStore(ListStore).getItems() // gives undefined
}
}
add(e){
this.context.executeAction(function (actionContext, payload, done) {
actionContext.dispatch('ADD_ITEM', {name:'toto', key:new Date().getTime()});
});
}
render() {
return (
<div>
<button className='waves-effect waves-light btn' onClick={this.add.bind(this)}>Add</button>
<TodoTable listItems={this.state.listItem}></TodoTable>
</div>
);
}
}
export default Todo;
As a Fluxible user you should benefit from Fluxible addons:
connectToStores.
The following example will listen to changes in FooStore and BarStore and pass foo and bar as props to the Component when it is instantiated.
class Component extends React.Component {
render() {
return (
<ul>
<li>{this.props.foo}</li>
<li>{this.props.bar}</li>
</ul>
);
}
}
Component = connectToStores(Component, [FooStore, BarStore], (context, props) => ({
foo: context.getStore(FooStore).getFoo(),
bar: context.getStore(BarStore).getBar()
}));
export default Component;
Look into fluxible example for more details. Code exсerpt:
var connectToStores = require('fluxible-addons-react/connectToStores');
var TodoStore = require('../stores/TodoStore');
...
TodoApp = connectToStores(TodoApp, [TodoStore], function (context, props) {
return {
items: context.getStore(TodoStore).getAll()
};
});
As a result you wouldn't need to call setState, all store data will be in component's props.