this.setState is not working correctly - javascript

I am using React 16 with Redux and i am using this.setState func onClick. But it is not working correctly. I tried to debug for hours and could not find any solution. Here is my code;
import React, { Component } from 'react';
import PropTypes from 'prop-types';
class SSelect extends Component {
state = {
selectedName: '',
}
handleSelect = (id, name) => {
this.setState({ selectedName: name });
this.props.handleSelect(id); // this is redux action
}
render() {
console.log(this.state.selectedName);
return (
<div>
{
this.props.options.map(option => (
<div
key={option.id}
role="button"
onKeyPress={() => this.handleSelect(option.id, option.name)}
onClick={() => this.handleSelect(option.id, option.name)}
tabIndex="0"
>
{option.name}
</div>
))
}
</div>
);
}
}
SSelect.propTypes = {
handleSelect: PropTypes.func.isRequired,
options: PropTypes.array.isRequired,
segmentIndex: PropTypes.number,
};
SSelect.defaultProps = {
segmentIndex: 0,
};
export default onClickOutside(SSelect);
console.log(this.state.selectedName); prints nothing, if i select same option on list, it prints true result. After select a new option, it prints empty again.
when i track my redux processes, i saw that it is working and sets new value to store correctly. when i remove my redux action, console.log(this.state.selectedName); printing true value.
here is my react and redux versions;
"react": "^16.2.0",
"react-redux": "^5.0.6",
"redux": "^3.7.2",
Thank you for your help.

To solve this problem a simple way, You can set the selectedName manually and then you can do force update.
handleSelect(id, name) {
this.state.selectedName=name;
this.forceUpdate();
this.props.handleSelect(id);
}
To redux setState
handleSelect(id,name){
this.setState({ selectedName: name })
};
onClick={this.handleSelect.bind(this,option.id, option.name)}

Unless my eyes are deceiving me, you have defined handleSelect as a class method and not an instance method meaning the this inside of the handleSelect function you have defined does not refer to your component.
You should define it as:
handleSelect(id, name) {
this.setState({ selectedName: name });
this.props.handleSelect(id); // this is redux action
}
rather than:
handleSelect = (id, name) => {
this.setState({ selectedName: name });
this.props.handleSelect(id); // this is redux action
}

I suppose you wrap the component with connect()?
As far is I know connect() makes you component pure (in redux terminology - props only) by default. It can be overridden in the fourth options argument of connect.
However most likely the best solution would be to get rid of internal state, because that's what we use redux for
[options] (Object) If specified, further customizes the behavior of
the connector. In addition to the options passable to
connectAdvanced() (see those below), connect() accepts these
additional options:
[pure] (Boolean): If true, connect() will avoid re-renders and calls
to mapStateToProps, mapDispatchToProps, and mergeProps if the relevant
state/props objects remain equal based on their respective equality
checks. Assumes that the wrapped component is a “pure” component and
does not rely on any input or state other than its props and the
selected Redux store’s state. Default value: true
https://github.com/reactjs/react-redux/blob/master/docs/api.md

This is very strange but i solved the problem.
On my reducer, my key's name was segment. I converted the name to selectedSegment and problem solved. I think segment word is reserved but i could not find any doc about reserved words.
I will open an issue to react-redux
Thank you all

Related

changeing state of one component from another component

There are numerous guides how a state can be stored in the context and how this state can be changed from any of the components. These examples store the state and an update function in the context.
But is it also possible to store the state somewhere else and store only the update function in the context?
The motivation of this question is that storing the state together with an updater function can be seen as a redundancy, which could be avoided.
I tried already many things and read much about this. But it seems not to work for me. But I don't understand why not. It should be possible that one component provides a setter function in the context and another component just calls this setter function.
I am aware, that this will only work if there is exactly one instance of the component, that provided the setter function.
Thanks to the help of one comment I found the answer.
The context in the following example is a function, which is visible in all components. Then in the component App there is a state and the setter. That setter is passed to the context. Once the setter is defined, it can be used by other components, such as the component GiveZag.
The good thing with this design is that the state and the way how it is updates is kept locally to where it belongs. It is often helpful to keep things as local as possible. Nothing of these details is revealed, except that there is a function, that can be called.
import React from 'react';
const ZigZagContext = React.createContext(
(newValue) => {console.log(newValue)}
);
class GiveZag extends React.Component {
render() {
return (
<ZigZagContext.Consumer>
{ setZigZag => (
<button onClick={() => setZigZag("zag")}>make zag</button>
)}
</ZigZagContext.Consumer>
);
}
}
class App extends React.Component {
setZigZag(newValue) {
this.setState({
zigzag : newValue
})
};
state = {
zigzag: "zig",
setZigZag: (newValue) => {this.setZigZag(newValue);}
};
render() {
return (
<ZigZagContext.Provider value={this.state.setZigZag}>
<h2>Current: { this.state.zigzag}</h2>
<p>Click button to change to zag</p>
<div><GiveZag /></div>
</ZigZagContext.Provider>
);
}
}
export default App;
Using the context is not always the best solution. This can be criticised in this case. The context enforces an unidirectional data flow.
Indeed the same can be achieved without the context mechanism. A solution that is simpler is the following code. This is not obvious and cannot be found so often in a web search. But it becomes clear when we keep in mind, that we have all features of JavaScript available. There is no need of using the context mechanism if not needed.
import React from 'react';
let ZigZagUpdater = (newValue) => {console.log(newValue)};
function GiveZag(props){
return (
<button onClick={() => ZigZagUpdater("zag")}>make zag</button>
);
}
class App extends React.Component {
setZigZag(newValue) {
this.setState({
zigzag : newValue
})
};
state = {
zigzag: "zig"
};
componentDidMount(){
ZigZagUpdater = (newValue) => {this.setZigZag(newValue);}
}
render() {
return (
<para>
<h2>Current: { this.state.zigzag}</h2>
<p>Click button to change to zag</p>
<div><GiveZag /></div>
</para>
);
}
}
export default App;

How to display react-redux state value without using setState?

This kind of problem has been answered before but I could not find a general way which works for all. Here is the state value screenshot:
The value came from node.js as
return res.send(available_balance);
Action was set as:
type: FETCH_DIGITAL_WALLET_BALANCE_BY_ID_SUCCESS,
digitalWalletUserAccountBalanceById: balance
As you can see the redux store has set the data right no doubt.
Now when I show the data as:
class UserUpdateModal extends React.Component {
constructor(props) {
...
}
render() {
return (
<div>
<h1>{this.props.initialValues.wallet_balance_by_id}</h1>
</div>
)
}
}
function mapStateToProps (state) {
return{
initialValues: {
wallet_balance_by_id: state.digitalWalletUserAccountBalanceById.data,
}
}
}
export default connect(
mapStateToProps,
)(withRouter(UserUpdateModal));
I get Error:
But when I create :
class UserUpdateModal extends React.Component{
this.state = {
wallet_balance_by_id:'',
}
this.getWallet_balance_by_id = this.getWallet_balance_by_id.bind(this);
}
getWallet_balance_by_id(){
this.state.viewWalletBalance==false?
this.setState({viewWalletBalance:true}):this.setState({viewWalletBalance:false})
}
....
}
Then call the function as input button it set the state and shows to the screen. So what is the basic way to shoe the redux state value to the screen without using a button to come around from the problem.
<h2>Balance</h2>
<h1>{ this.state.viewWalletBalance ?
this.props.initialValues.wallet_balance_by_id : null }</h1>
<input type="submit" value="Balance" onClick={this.getWallet_balance_by_id} />
{/* <h1>{this.props.initialValues.wallet_balance_by_id}</h1> */}
As you can see the redux store has set the data right no doubt.
The redux store gets the right value eventually but it doesn't always have the right value. Look again at the screenshot that you posted of Redux Dev Tools. Eventually the value is a number 10.12. But initially the value is an empty object {}. Why?
The problem is not in your component code or any of the code that you have included here. The problem is the initial state of your Redux store, which is setting the state.digitalWalletUserAccountBalanceById.data property to an empty object {}. Fix the initial state and your problems will go away. It should be a number or undefined.
From the screenshot, ( and mapStateToProps ) it seems like the initial value of wallet_balance_by_id is an Object ( which is state.digitalWalletUserAccountBalanceById.data ) which will throw that error because you can't render the Object like that, this is happening before the state update
{} => 10.12
It works when you click on the button because the value changes to a number and you can legally render it
update mapStateToProps to :
function mapStateToProps (state) {
return{
initialValues: {
wallet_balance_by_id: state.digitalWalletUserAccountBalanceById.data.wallet_balance_by_id,
// ^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^
// this should not be an object
}
}
}
Or better :
function mapStateToProps (state) {
return{
initialValues: state.digitalWalletUserAccountBalanceById.data
}
}

React component not re-rendering, although Object in props changes

I know, there are many, many similary questions.. **duplicate alarm!**
But: I looked through all of them, I promise. I'm quite sure now, that this is another case, that could have to do with the props being an object (from what I've read here). But I couldn't solve the following, anyway:
class CsvListDropdown extends Component {
constructor(props) {
super(props);
this.state = { sessions: props.sessions }
this csvsInSession = this.csvsInSession.bind(this);
}
csvsInSession(sessions) {
return (sessions
.map(keys => Object.entries(keys)[2][1])
.map((csv, i) => (
<option value={csv} key={i}>{csv}</option>
))
)
}
render() {
const { isLoading } = this.props
if (isLoading) { blablabla.. }
else {
return (
...
<select value={this.props.sessions[0].currentCsv}>
{this.csvsInSession(this.state.sessions)}
</select>
...
)
}
}
}
export default withTracker(() => {
const handle = Meteor.subscribe('sessions');
return {
isLoading: !handle.ready(),
sessions: Sessions.find({}).fetch()
};
})(CsvListDropdown);
Now from the client I am writing another document into the Sessions collection, containing the .csv filename, while this new csv file is being uploaded to a remote server. console.log(this.props.sessions) gives me an array, which is up to date. But the component itself does not re-render.
What I also don't understand is: console.log(this.state.sessions) returns undefined. (note: state)
What I tried so far:
{this.csvsInSession(this.props.sessions)} (note: props)
Adding a withTracker / State / Props to the parent component and passing the sessions object from either state or props as params to the child component, that should re-render.
forceUpdate()
componentWillUpdate()
What may be important as well: The component should re-render about the same time another component also re-renders (which displays the contents of uploaded CSVs, that return from a microservice and get written into another collection). The latter does actually re-render.. But that dropdown does not.. argh!
this.state will only change if you call this.setState(), which you are not doing. You are initializing state with a value from props, but only in the constructor when the component is first instantiated. After that, even if props changes your component may re-render but what it displays won't change because state hasn't been updated.
In fact, there does not appear to be any reason whatsoever to store data in state in that component. It might as well be a functional presentational component:
function CsvListDropdown(props) {
function csvsInSession(sessions) {
return (sessions
.map(keys => Object.entries(keys)[2][1])
.map((csv, i) => (
<option value={csv} key={i}>{csv}</option>
))
)
}
const { isLoading } = props;
if (isLoading) { blablabla.. }
else {
return (
...
<select>
{csvsInSession(props.sessions)}
<select>
...
)
}
}
Generally all of your components should be stateless functional components unless they specifically need to store internal state for some reason.
Now I finally solved it, and it turns out that the component did actually update at any time, but I did not notice it, simply because the latest item in the array was quietly appended to the bottom of the dropdown list. This however I was not expecting, as I had published the collection with a descending sorting.
// server-side
Meteor.publish('sessions', function() {
return Sessions.find({ userId: this.userId }, { sort: {createdAt: -1} });
});
Server-side seems to be the wrong place to sort. It simply does not have an effect. So sorted on the client side, when subscribing:
// client-side
export default withTracker(() => {
const handle = Meteor.subscribe('sessions');
return {
isLoading: !handle.ready(),
sessions: Sessions.find({}, { sort: {createdAt: -1} }).fetch()
};
})(App)
I had omitted an important detail from my question, that is how I set the value of the dropdown field:
<select value={this.props.sessions[0].currentCsv}>
{this.csvsInSession(sessions)}
</select>
So lesson learned: If you think your react component does not re-render, always check if that's true, before assuming so.
As a side effect of debugging I restructered my components. Now the Meteor.subscribe() is within the parent component, that contains all the children, that have to handle the sessions object. And the sessions object gets passed down from the parent to the (grand)children as props. I think it's more readable and easier to maintain that way.

React with Redux: error on propType property

I'm new in the React world. I got a course to training React and Redux.
Like yesterday I got an error while I'm attending an online training
Even though, I walk through the author course and copy the code from the screen I get an error:
Warning: Failed propType: Required prop courses was not specified in CoursesPage. Check the render method of Connect(CoursesPage).
I have uploaded my code to github: https://github.com/tarcisiocorte/reactredux/blob/master/src/components/course/CoursesPage.js
again....I will appreciate some help.
import React, {PropTypes} from "react";
import {connect} from 'react-redux';
import * as courseActions from '../../actions/courseActions';
class CoursesPage extends React.Component {
constructor(props, context){
super(props, context);
this.state = {
course:{title: ""}
};
this.onTitleChange = this.onTitleChange.bind(this);
this.onClickSave = this.onClickSave.bind(this);
}
onTitleChange(event){
const course = this.state.course;
course.title = event.target.value;
this.setState({course: course});
}
courseRow(course, index){
return <div key={index}>{course.title}</div>;
}
onClickSave() {
this.props.dispatch(courseActions.createCourse(this.state.course));
}
render() {
return (
<div>
<h1>Courses</h1>
{this.props.courses.map(this.courseRow)}
<h1>Add Courses</h1>
<input
type="text"
onChange={this.onTitleChange}
value={this.state.course.title} />
<input
type="submit"
value="Save"
onClick={this.onClickSave} />
</div>
);
}
}
CoursesPage.propTypes = {
dispatch: PropTypes.func.isRequired,
courses: PropTypes.array.isRequired
};
function mapStateToProps(state, ownProps) {
return{
courses: state.courses
};
}
export default connect(mapStateToProps)(CoursesPage);
In https://github.com/tarcisiocorte/reactredux/blob/master/src/index.js#L11
You need to specify a default for courses.
You have specified that your courses prop is required:
courses: PropTypes.array.isRequired
so you need to pass in something from the redux store and by the looks of it the courses property in your redux store is undefined. (Put a breakpoint here to check that is actually the case)
You can either make sure your redux store always returns something for your courses or your can remove the isRequired constrain:
CoursesPage.propTypes = {
dispatch: PropTypes.func.isRequired,
courses: PropTypes.array
};
In your 'Routes' component, you'll want to change
<Route path="courses" component={CoursesPage} />
to
<Route path='courses' render={(stuff) => (
<CoursePage courses={stuff} />
)}/>
When you use component, you can't add your required props, so render would be a good alternative. This also means you'll have to add redux connections to your routes.js since you need to get that information from somewhere.
Another, more simpler, solution would be just to eliminate courses as a prop and get that information directly from redux when CoursePage loads up. You've already done half the battle with your mapStateToProps, therefore you dont need to have it with the "isRequired" in your propTypes. This is basically when Klugjo said, so if you decide to take this approach, give him credit.
I'd also hazard a guess that if 'courses' in your store doesn't exist, your isRequired is being triggered as well. So you might be able to keep isRequired as long as you have your data for that prop in the store.
For anyone coming across a similar failed prop type error, such as below, or if the other answers did not resolve your issue, the following might be an alternate fix for you. In the context of user428745's post above, someProjects and ResponsiblePage in the error below would correspond to the courses prop (some array of values) and the CoursesPage component, respectively.
Given user428745's setup below
CoursesPage.propTypes = {
dispatch: PropTypes.func.isRequired,
courses: PropTypes.array.isRequired
};
function mapStateToProps(state, ownProps) {
return {
courses: state.courses
};
}
The issue might be related to how the redux state gets the state.courses value in the first place. In my case, the prop (ie. courses as in state.courses) in mapStateToProps was being set before the data was available from the redux store. This happened due to an API data call that had not yet finished. My fix was:
function mapStateToProps(state, ownProps) {
return {
courses: state.courses || [] // Equivalent to statement below
//courses: state.courses == null ? [] : state.courses
};
}
If state.courses is null (due to API data not loaded yet) we return [] to satisfy the array requirement on our prop. If it is valid, which means the data was available and was put inside of state.courses, then we simply return state.courses similar to before.
Note also that there might be different required configuration setups (to make redux work properly), ie. depending on how you link your reducer(s) to your root reducer (which would be the content in index.js inside of reducers folder). If the error is still not fixed with these changes, try another approach with the root reducer, such as:
// From this (see `user428745`'s source files, where 'courseReducer' was imported as 'courses')
export default combineReducers({
courseReducer
});
// To this
export default combineReducers({
rootReducer: courseReducer
});
// Where 'mapStateToProps' would also have to change
function mapStateToProps(state, ownProps) {
return {
courses: state.rootReducer.courses || []
};
}
And where you intend to use this value, ie. with this.props.courses or props.courses in your CoursesPage setup, you could console log the values (or whatever you wanted to do) only when the array is not empty:
if (props.courses.length > 0) {
console.log(props.courses);
}
Or maybe listen to props.courses changes so that you perform something only "in the moment" after it changes (whereas the if statement above would be valid at all times, from when the prop was filled with values):
useEffect(() => {
if (props.courses.length > 0) {
console.log(props.courses);
}
}, [props.courses]);
Note that if you use useEffect, make sure it is within your CoursesPage component, and not in the "root" of the file where you would ie. write export default CoursesPage.

Call to function on react

How can I call a function _toggleDropdown or _onWindowClick from another class and file?
DropDown.js
export default class DropDown extends React.Component {
_toggleDropdown(e) {
e.preventDefault()
this.setState({
isActive: !this.state.isActive
})
}
_onWindowClick(event) {
const dropdownElement = findDOMNode(this)
if (event.target !== dropdownElement && !dropdownElement.contains(event.target) && this.state.isActive) {
this.setState({
isActive: false
})
}
}
}
Header.js
<a onClick={what???}>test</a>
If DropDown component is rendered within Header you can use refs to get dropdown instance and call its methods.
Header.js
render() {
return (<div>
<DropDown ref="dd"/>
<a onClick={e => this.refs.dd._toggleDropdown(e)}>Toggle</a>
</div>)
}
If they are totally unrelated you'd better switch from local state to some global state management solution like flux or redux. And make dropdown state to be a part of global application state that any component could change by dispatching corresponding action.
Well the only way to do this kind of thing is by passing the function as props to the Header component. I am not sure about your structures to make a clean snippet with the results. Maybe the design is not being clear enough to make it easy for you.

Categories