How to map a redux state slice to a local component state? - javascript

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)
}

Related

React app: props do not get updated externally

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.

Binding Props to State ReactJS

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));

React: componentDidMount + setState not re-rendering the component

I'm fairly new to react and struggle to update a custom component using componentDidMount and setState, which seems to be the recommended way of doing it. Below an example (includes an axios API call to get the data):
import React from 'react';
import {MyComponent} from 'my_component';
import axios from 'axios';
export default class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
data: []
};
}
GetData() {
return axios.get('http://localhost:5000/<route>');
}
componentDidMount() {
this.GetData().then(
(resp) => {
this.setState(
{data: resp.data}
)
}
)
}
render() {
return (
<MyComponent data={this.state.data} />
);
}
}
Doing console.log(this.state.data) just below render() shows that this.state.data does indeed get updated (from [] to whatever the API returns). However, the problem appears to be that MyComponent isn't rendered afresh by componentDidMount. From the Facebook react docs:
Setting state in this method will trigger a re-rendering.
This does not seem to be the case here: The constructor of MyComponent only gets called once (where this.props.data = []) and the component does not get rendered again. I'd be great if someone could explain why this is and whether there's a solution or a different way altogether to get the updating done.
UPDATE
I've added the code for MyComponent (minus some irrelevant features, as indicated by ...). console.log(data_array) prints an empty array.
import React from 'react';
class DataWrapper {
constructor(data) {
this._data = data;
}
getSize() {
return this._data.length;
}
...
}
export class MyComponent extends React.Component {
constructor(props) {
super(props);
this._dataWrapper = new DataWrapper(this.props.data);
this.state = {
data_array: this._dataWrapper,
};
}
render() {
var {data_array} = this.state;
console.log(data_array);
return (
...
);
}
}
You are falling victim to this antipattern.
In MyComponent constructor, which only gets called the first time it mounts, passed your empty array through new DataWrapper and now you have some local state which will never be updated no matter what your parent does.
It's always better to have one source of truth, just one state object anywhere (especially for things like ajax responses), and pass those around via props. In fact this way, you can even write MyComponent as a simple function, instead of a class.
class Example extends Component {
state = { data: [] }
GetData() { .. }
componentDidMount() {
this.GetData().then(res =>
this.setState({data: new DataWrapper(res.data)})
)
}
render() { return <MyComponent data={this.state.data} /> }
}
...
function MyComponent (props) {
// props.data will update when your parent calls setState
// you can also call DataWrapper here if you need MyComponent specific wrapper
return (
<div>..</div>
)
}
In other words what azium is saying, is that you need to turn your receiving component into a controlled one. Meaning, it shouldn't have state at all. Use the props directly.
Yes, even turn it into a functional component. This helps you maintain in your mind that functional components generally don't have state (it's possible to put state in them but ... seperation of concerns).
If you need to edit state from that controlled component, provide the functions through props and define the functions in the "master" component. So the master component simply lends control to the children. They want anything they talk to the parent.
I'm not posting code here since the ammendment you need to make is negligible. Where you have this.state in the controlled component, change to this.props.

Multiple state version merge for React

I have a single state inside my React component which has the structure like this
{a: {x: 0}, b: {x:0}}. During the operation time, i need to trigger several requests to server in order to request the data in order to update the state, and this need to be updated in the state. Since the number of the record in the database is quite big, I have to trigger several times
If I do like this
request_data(e) {
['a', 'b'].forEach((k) => {
// do not mutate the state directly
let new_state = _.extend({}, state);
request(params, (err, res) => {
// set result to the new_state
new_state = res;
// update the original state
this.setState(newState);
})
});
}
inside the callback, the second request will not have the data of the first request the moment it request and update the origin state with the empty value for the first branch of the state structure.
One simple solution is updating the origin state directly, but I don't think it is a good idea. I am planning to integrate redux later but at this moment, since the current code base is quite big, migrating gradually is better. However, I dont' want to alter the origin state directly since it is not the redux way. Any suggestion to overcome this issue ?
I'm assuming by the way your code is written, you're using es6 classes for your component? That being the case, hopefully this tidbit will help:
import React from 'react'
class MyComponent extends React.Component {
constructor(props) {
super(props)
this.state = {
foo:'bar'
}
}
request_data = (e) => {
['a', 'b'].forEach((k) => {
// do not mutate the state directly
let new_state = _.extend({}, state);
request(params, (err, res) => {
// set result to the new_state
// update the original state
this.setState(foo:res.foo)
})
});
}
render() {
return (
// component code here
)
}
}
export default MyComponent
Notice that I'm using an arrow function for the request_data part...this is to avoid need to bind the function to the this variable inside of the constructor.
UPDATE:
I'm still not quite sure I understand what problem you're having here...but if you're not wanting to use redux right now, you could go with a container component method. Basically, your container component's main job is to manage the data and do nothing but pass its updated state to a child component as props. Something like this:
class MyComponentContainer extends React.Component {
constructor(props) {
super(props)
this.state = {
a: {
x: 0
},
b: {
x:0
}
}
}
componentWillMount() {
['a', 'b'].forEach((k) => {
request(params, (err, res) => {
this.setState({
a: res.a,
b: res.b
});
})
});
}
render() {
return (
<MyComponent data={this.state} />
)
}
}
class MyComponent extends React.Component {
constructor (props) {
super(props)
}
render() {
return (
<div>
{/* display stuff here */}
</div>
)
}
}
export default MyComponentContainer

Reactjs, parent component, state and props

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.

Categories

Resources