React/Redux mapStateToProps by ownProps - javascript

I'm trying to add a component in react which will get updates from websockets (already working) according its property:
<div><Temperature room='livingroom'/></div>
<div><Temperature room='cellar'/></div>
The update should only be done for the component with the room property, which is mapped with mapStateToProps:
function mapStateToProps(state, ownProps){
if (ownProps.room == state.dataReducer.room_name){
return { temp: state.dataReducer.temp,
room_name: state.dataReducer.room_name}
}
}
export default connect(mapStateToProps)(TOB);
The component definition:
const Temperature = ({room_name, temp}) => (
<ul> {temp} </ul>
);
Now every update I get (can happen async) one component is blank while the other is filled by the right temperature so it starts to flicker on frequent updates.
Do you have any idea how to do this updating by properties the right and working way?

You should use shouldComponentUpdate on your Temperature component to decide if you want it to render or not. The component will render only if the return value evaluate to true
shouldComponentUpdate(nextProps) {
return nextProps.room === nextProps.room_name;
}
function mapStateToProps(state){
return
{ temp: state.dataReducer.temp,
room_name: state.dataReducer.room_name
}
}
export default connect(mapStateToProps)(TOB);

Your mapState function should always return the data this component needs, regardless of what action has been dispatched or how the store has been updated. React-Redux will then check to see if the data for this component has actually changed since last time, and only re-render it if the data has changed.
Right now, your mapState function is only returning data some of the time. You should rewrite it so that every instance of Temperature is always extracting whatever data it needs from the store.
(As a side note: don't name state slices as "state.somethingReducer". Name them after the data that's contained inside.)

Related

Redux, react. How I can add mapStateToProps into this.state?

I have action and middleware, where I make a fetch request.
I get a data in mapStateToProps.
But I want use this data in my state and change them in setState.
How I can add mapStateToProps into this.state?
I want filter my data on client side.
If I dispatch my action I was make request to my server, it does not have to be done, because we have all list in our store.
You can achieve this by updating component's state once a redux store is updated (say, dispatched fetch success action) and component receives new values.
However, this is not idiomatic in most cases, since now you have to deal with possible desync of local component state and external redux state (which can be updated from outside of our component AND these new values will be passed down to our component).
Of course, you can ignore those changes and do nothing on props change.
Or reverse - update the state of component every time new props arrive.
Here is an example of syncing props and state:
// get `value` from store and pass it as prop to our component
const mapStateToProps = state => ({ value: state.someKey.value })
class Component extends React.Component {
// new props has been passed
// this method receives new props and current state
// returns next state values, or null if no changes required
static getDerivedStateFromProps(props, state) {
// some complex comparison logic goes here
// if true, then desync happened, let's update local state
if (state.value !== props.value) {
// this value will be at `this.state.value`
return { value }
}
// else, no update needed for our state
return null
}
// initial values taken from props
state = { value: this.props.value }
// update local state
onChange = e => this.setState({ value: e.target.value })
// let's render props value as label and state values as input value
render() {
return (
<label>
Props value is {this.props.value}
<input value={this.state.value} onChange={this.onChange} />
</label>
)
}
}
Note, that getDerivedStateFromProps is a relatively new lifecycle method (similar to "old" componentWillReceiveProps) and this use case is mentioned in official docs as a possible sign of bad design.
TL;DR duplicating state is bad, especially if we have to sync it manually.

Update component according to redux

I want to create an app with react and redux. My component subscribed to several states from the redux store, some of the state-data need to be prepared before the rendering can take place. Do I need to put the prepareData function into componentWillReceiveProps and write it to the state afterwards? It seems to create a lot of queries in the componentWillReceiveProps. Is there a best practice?
componentWillReceiveProps(nextProps) {
if (this.props.dataUser !== nextProps.dataUser) {
this.prepareData(nextProps.dataUser);
}
if (this.props.dataProject !== nextProps.dataProject) {
.....
}
if (this.props.dataTasks !== nextProps.dataTasks) {
.....
}
}
As Axnyff suggests, you can do your data preparation in mapStateToProps, this will trigger a render each time your redux state updates (your component can be stateless this way) :
mapStateToProps = (state) => {
const dataUserPrepared = prepareData(state.dataUser);
return { dataUser: dataUserPrepared };
}
If you have a lot of different data to prepare, which updates individually, that can be a loss in performance.
In this case you can use componentWillReceiveProps like in your question, this is fine because the setState in your prepareData() function will be batched with the received props to trigger only one render per prop update.
If you were using an app without redux then the solution would be to prepare your data before you call this.setState().
I believe the same solution applies to when using redux, your can prepare your data inside your action because you return the action object having a type and payload.
You can also prepare your data inside your reducer before returning the state object.
You could even prepare your data inside mapStateToProps of your component.
But in case you want to specific conditions under which component should re-render when state changes, then you do that in shouldComponentUpdate()

Calling state changing functions from mapStateToProps

I am new to react-redux and I was surprised to see an example where a function, in this case being getVisiblieTodos, is called inside mapStateToProps. This function should be called in a reducer since it changes state? Is the code breaking "good form" for the sake of brevity? Is it okay to do this in general?
I am looking at code from this link
import { connect } from 'react-redux'
import { toggleTodo } from '../actions'
import TodoList from '../components/TodoList'
const getVisibleTodos = (todos, filter) => {
switch (filter) {
case 'SHOW_ALL':
return todos
case 'SHOW_COMPLETED':
return todos.filter(t => t.completed)
case 'SHOW_ACTIVE':
return todos.filter(t => !t.completed)
}
}
const mapStateToProps = (state) => {
return {
todos: getVisibleTodos(state.todos, state.visibilityFilter)
}
}
const mapDispatchToProps = (dispatch) => {
return {
onTodoClick: (id) => {
dispatch(toggleTodo(id))
}
}
}
const VisibleTodoList = connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)
export default VisibleTodoList
In redux we want the store to hold the minimal data needed for the app. Everything that is derived from the base data, should be computed on the fly, to prevent cloning pieces of the store, and the need to recompute all derived data when something changes in the store.
Since the visible todos list is not part of the store, but computed using the list of todos, and the visibilityFilter, the getVisibleTodos() doesn't change the store's state. It produces the derived computed data from the those two properties.
A function that is used to get data from the store, and compute derived data is known as a selector. Using selectors, the derived data is not part of the store, and computed when needed. In addition, we can use memoized selectors, to save the computation overhead.
You may see getVisibleTodos as a reducer because it includes "switch .. case" block or/and because it has 2 arguments . However, it is not a rule.
A redux reducer ( by definition) changes store state according to dispatched action , and that's why it takes two arguments ( store state + dispatched action ) and it returns new state for the store without mutation.
getVisibleTodos here is a helper function which filter an array according to string (filter).
Also , filter is not a redux-action, it is just string that decides todos to be rendered.
I may agree with you it is something weird , and if we can see the whole application (reducers, actions,... ) we can decide if it is best practices or not .
todos in this component is a calculated property based on the state of the reducer, and it is not changing any state.
It's okay to transform properties comming from recuders that are used only by one component (they are called selectors). Imagine that you use todos in other components, you will not want to make changes in one component like filtering and seeing that in the other components. If this is the case, it's fine to do it.
Also, it is a good property of your reducer to store only the needed data. More state is more complexity in the app, and more overhead to calculate new states.
It seems to me that a function should do what its name says, nothing less, nothing more.
mapStateToProps() should just do that, ie "map", and should normally not call other functions.

Sync React state to Meteor collection using debounce

I have a textbox in my Meteor + React application. I want to sync its value to a Mongo collection. However, I don't want to update the collection after every keystroke, only when the user has stopped typing for a few seconds.
The textbox in my render() function looks like this:
<input type="text" ref="answer" onChange={this.onChange} value={this.state.someValue} />
I store the textbox value in this.state instead of this.data because this.data reflects the Mongo collection, which might have not been updated yet.
So far, all of this works.
The problem:
If another client updates the collection, I want the textbox to show the updated value. For this I have to update this.state inside the getMeteorData() function, but that's disallowed, and I get an error: "Calling setState inside getMeteorData can result in infinite loop".
Right now I have a workaround where I manually update the textbox value in componentDidMount() and getMeteorData(), but it feels hackish and I don't like it at all.
Is there a better way to do this? Can I maybe force state updates inside getMeteorData() if I promise I'll be a good boy and behave nicely?
I would get rid of getMeteorData at all and turn to createContainer. Data flow gets clear and simple most of the time, including this specific case. Here it goes.
First thing first, create a container to fetch data.
export default theContainer = createContainer(() => {
// Subscribe to the publication which publishes the data.
const subscription = Meteor.subscribe(...);
// Derive the data for the input box and form the props to pass down.
const props = {
answer: getAnswer(subscription)
};
return props;
}, theComponent);
theContainer acts as a container component and transferes the contained data to the presentational component theComponent by props. Be noted that the function given to createContainer is responsive, meaning that changes to reactive data sources in that function trigger rerun and result in rerender of theComponent.
By now we are all armed. Since data in the Mongo collection (Minimongo exactly) is synced by the props passed down, theComponent is aware of the synchronization by a prop transition.
export default class theComponent extends React.Component {
...
componentWillReceiveProps(nextProps) {
if (this.props.answer !== nextProps.answer) {
this.setState({
answer: nextProps.answer
});
}
}
render() {
return <input value={this.state.answer} onChange={this.onChange} />;
}
}
While such transition occurs, the upcoming value is updated to the state, and this controlled component will render the input based on the updated new value.
On the other hand, while the user starts typing, the change handler this.onChange updates the user's input to the state with every key stoke for this is a controlled component. However, the handler updates the Mongo collection (again, Minimongo exactly) only when the preset duration has elapsed to save data transmission.

Why componentWillMount is called after rendering?

I am working with React and I am trying to understand the lifecycle. I am doing a componentWillMount method in order to get the props I need before the render occurs. I need to know how to update the state when the view loads.
All I am trying to do is a GET request in order to get a list of dealers for a Casino Game. Basically, I am missing 1 or 2 steps which are for render the dealers's list in the DOM
I will show what I am doing with my code and after that I will explain what I want
Actions part
getDealerActions.js
class GetDealersActions {
constructor () {
this.generateActions('dealerDataSuccess', 'dealerDataFail');
}
getDealers (data) {
const that = this;
that.dispatch();
axios.get('someroute/get-dealers/get-dealers')
.then(function success (response) {
that.actions.dealerDataSuccess({...response.data});
})
}
};
then we move to the stores
getDealersStore.js
class GetDealersStore {
constructor () {
this.state = {
dealerData : null,
};
}
#bind(GetDealersActions.dealerDataSuccess)
dealerDataSuccess (data) {
this.setState({
dealerData : data,
});
console.log(this.state.dealerData);
}
}
in this case that console.log(this.state.dealerData); returns something like this which is exactly what I need
Object {dealersData: Array[3]}
the problems comes in the component part, honestly because I don't know how to handle the data here
#connectToStores
export default class Dealers extends Component {
static contextTypes = {
router : React.PropTypes.func,
}
constructor (props) {
super(props);
this.state = {}
}
static getStores () {
return [ GetDealersStore ];
}
static getPropsFromStores () {
return GetDealersStore.getState();
}
componentWillMount () {
console.log('###', this.props);
GetDealersActions.getDealers();
}
render () {
console.log('>>>', this.props);
let content;
if (this.state.dealerData) {
content = this.state.dealerData.map((item) => {
return <div key={item.CardId}>{item}</div>;
});
} else {
content = <div>Loading . . .</div>;
}
return (
<div>
<div>{content}</div>
</div>
);
}
}
all I get here <div>{content}</div> is Loading . . . because this.state is coming like this Object {}
A weird situation I am getting here, is that this view is rendering twice, the 1st time is rendering, and the console.log('>>>', this.props); returns this >>> Object {params: Object, query: Object} and the second time it renders, fires this >>> Object {params: Object, query: Object, dealerData: Object} which is what I need.
So, why componentWillMount is waiting the render method in order to get fired ?
It's not weird at all. componentWillMount will fire before render, and in the first-pass you are invoking an action to get the dealers GetDealersActions.getDealers(); which is basically an async command. Since it is async, the component will render once before it gets data, and then again after the store publishes a changed event, which will re-trigger rendering.
Here is an approximation of the sequence of actions happening in your example:
componentWillMount invokes getDealers command (which is async)
initial render with default component state
Async operation completed in action creator and store is set with dealer data
store publishes a changed event, which re-triggers rendering
second render invoked with the dealer data in component state.
The problem is that React will run it's lifecycle methods in a certain sequence, not caring about you invoking some async method. So basically you don't have a way to stop rendering just because you invoked a command to get the dealers. That is a limitation of react (or a feature), which surfaces when combined with async programming and you should accept it as is.
If you accept the fact that React will render twice, you can utilize that in your favor, so on first render you could just show a loading indicator (e.g. a spinning wheel) and when the data loads you just display it in the second render.
However, if you are not convinced and still want to avoid double-rendering in the initial load, you could do prefetching of the data before you mount the application component, which would ensure that initial data is loaded in the store before the first render, which would mean that you wouldn't have to invoke getDealers in componentWillMount since the data would already be in the store on the first render.
As a reminder, double-rendering is not a significant performance problem, like it would be in Angular.js or Ember.js, since React is very efficient at DOM manipulation, but it could produce some UX issues if not handled properly.

Categories