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));
Related
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.
Currently I'm using the following:
const mapStateToProps = state => {
return {
stateInProps: state.someSliceIWant
};
};
Foo.propTypes = {
dispatch: PropTypes.func,
stateInProps: PropTypes.object
};
export default connect(mapStateToProps)(Foo);
and since we can only actually map state to props, and not directly to state in my component, I'm getting around this in a hacky way by doing this:
class Foo extends React.Component {
constructor(props){
super(props);
this.state = {};
setTimeout(() => {this.setState(this.props.stateInProps);},1);
}
How should I do this properly? I've tried using different lifecycle hooks and all sorts of methods.
An interesting thing I found was that if I put setState() in mapStateToProps.... it ALMOST works.
const mapStateToProps = state => {
Foo.setState(state.someSliceIWant);
return {
stateInProps: state.someSliceIWant
};
};
It throws a bunch of errors, but when I delete that line and webpack grinds through and the page re-renders, the state has actually been updated properly and the values are there! WHAT?! I can't figure out how to get it do that, without throwing the errors, as Redux/React itself won't allow it as best I can tell.
Have you tried ComponentWillReceiveProps after mapping state to Props?
For instance:
componentWillReceiveProps(nextProps) {
if (nextProps.errors) {
this.setState({
errors: nextProps.errors
});
}
}
The hacky way you are using, you might not able to access your stateInProps if it get updated in later lifecycle.
You can use something as below
class Foo extends React.Component {
state = {};
componentWillMount() { this.setStateInProps(this.props); }
componentWillReceiveProps(nextProps) { this.setStateInProps(nextProps)}
setStateInProps= (props) => this.setState(props.stateInProps)
}
I'm new to react.js.
I'd like to get default value of state following_status by receiving props.user.following_status.
I'm passing user object ( user = { following_status: 'following', id:123 } ) to ReactionButton component. ReactionButton component is looks like this:
class RelationButton extends React.Component {
constructor(props){
super(props);
console.log(props.user.following_status) # undefined!!!
this.state = {
following_status: props.user.following_status
}
...
render() {
if (this.state.following_status == 'following') {
<UnFollowBtn/>
} else {
<FollowBtn/>
}
}
RelationButton was called by UserCardHeader component.
const UserCardHeader = (props) => {
const user = props.user;
return(
<header className="user-card--full__header">
<RelationButton user={user}></RelationButton>
</header>
)
}
I don't understand why console.log(props.user.following_status) returns undefined. I googled many websites like those:
React component initialize state from props
accessing props inside react constructor
those answers suggest
class FirstComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
x: props.initialX
};
}
}
but this didn't work for me.
If I add componentWillReceiveProps to the codes above,
componentWillReceiveProps(props){
console.log(props.user.following_status) #=> "following"
this.setState({following_status: props.user.following_status})
}
everything works well. However I think it's weird solution and sometimes doesn't work. Why can't I receive object props in constructor(props) {} section?
Without the full code, we can't tell what's wrong but it is obvious that following_status comes asynchronously to the component and that's why is not accessible right away in the constructor.
To somehow fix it you can detect if props have changed and reset state accordingly in componentDidUpdate.
class RelationButton extends React.Component {
constructor(props){
super(props);
console.log(props.user.following_status) # undefined!!!
this.state = {
following_status: props.user.following_status
}
}
componentDidUpdate(prevProps) {
if(prevProps.user.following_status !== this.props.user.following_status) {
this.setState({ following_status: this.props.user.following_status })
}
}
render() {
// you forgot about return statements :
if (this.state.following_status == 'following') {
return <UnFollowBtn/>
} else {
return <FollowBtn/>
}
}
}
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.