React State Not Updating with Conditional Statement - reactjs

I have this really strange issue with React.
The following below WORKS. It calls the action creator fetchUserForm which then fetches an object and sets it to the redux storage called userForm. userForm is then called in step1Component if it is loaded.
class FormEdit extends Component {
constructor(props) {
super(props)
this.nextPage = this.nextPage.bind(this)
this.previousPage = this.previousPage.bind(this)
this.updateComponents = this.updateComponents.bind(this)
this.state = {
page: 1,
}
}
componentWillMount() {
this.props.fetchUserForm(this.props.params.id);
}
render() {
const { page } = this.state
return (
<div>
{page === 1 && <Step1Page nextPage={this.nextPage}/>}
</div>
)
}
}
function mapDispatchToProps(dispatch){
return bindActionCreators({fetchUserForm}, dispatch);
}
function mapStateToProps(state) {
return { userForm: state.newForm.userForm,
};
}
export default connect(null, mapDispatchToProps)(FormEdit);
Reducer:
const INITIAL_STATE = {
userForm:'false'
};
case FETCH_USER_FORM:
console.log("----------------> REDUCER: updating custom form");
console.log(action.payload);
return {...state, userForm: action.payload };
Step1Page Component:
render() {
if(!this.props.userForm){
return(
<div> LOADING </div>
)
}
return(
<div> Actual Content </div>
)
The above works perfectly. However, this is where my strange issue occurs. I want to do something with the userForm in the FormEdit component. I can't use the form until it fully loads so I add this to FormEdit:
if(!this.props.userForm){
return(
<div> LOADING </div>
)
}
return(
<div> "Actual Content </div>
)
EXCEPT when I add this to FormEdit, I'm stuck at the LOADING div forever. When I view the react tools, it says that the userForm is set to false.This makes no sense because when I view the console.log it says:
Which means it was passed to the reducer. However, even when it's passed, looking at react tools it says that the userForm is still "false".
IF I remove the conditional in FormEdit, everything works again and the userForm is filled with the objects. So I'm really confused why the conditional in my FormEdit component is causing such an issue. When it's not added, everything works fine. But when it is added, the reducer state remains false.

In FormEdit you don't have the userform property.
You have to pass mapStateToProps into the connect function.
export default connect(mapStateToProps, mapDispatchToProps)(FormEdit);

Related

Cannot pass data between components in React/Redux

I'm new to React and am trying to build an app which shuffles football players into two teams and am having difficulty with passing data from one component to another. I have redux and react-redux installed.
My problem is that once 10 names have been inputted (which I am calling numbersReached), the button to submit the addPlayers form should be disabled so no more players can be submitted.
I have tried passing a state value, numbersReached, into my AddPlayers component, but it is not being imported correctly - it is showing in console.log as undefined.
My code so far:
'initialState.js'
export const initialState = {
playersList: [],
shuffledList: [],
teamA: [],
teamB: [],
numbersReached: false
};
export default initialState;
'src\components\NumbersReached\index.js':
import { connect } from "react-redux";
import NumbersReached from "./NumbersReached";
const mapStateToProps = (state) => {
return {
...state,
numbersReached: state.playersList.length >= 10 // Once state.playersList.length>=10 numbersReached should = true. (10 is for five-a-side)
};
};
export default connect(mapStateToProps)(NumbersReached);
src\components\NumbersReached\NumbersReached.js:
import React from "react";
const NumbersReached = ({ numbersReached }) => (
<div>
{numbersReached ? "Numbers Reached" : null}
</div>
);
export default NumbersReached;
'src\components\AddPlayer\index.js':
import { connect } from "react-redux";
import AddPlayer from "./AddPlayer";
import { addPlayer } from "../../data/actions";
const mapStateToProps = (state) => {
return {
playerName: state.playerName,
numbersReached: state.numbersReached
};
};
const mapDispatchToProps = (dispatch) => {
return {
handleSubmit: (data) => dispatch(addPlayer(data)),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(AddPlayer);
'src\components\AddPlayer\AddPlayer.js'
import React, { Component } from 'react';
class AddPlayer extends Component {
constructor(props) {
super(props);
this.state = {
playerName: props.playerName,
numbersReached: props.numbersReached
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
};
handleChange(e) {
this.setState({ playerName: e.currentTarget.value });
}
handleSubmit(e) {
e.preventDefault();
this.props.handleSubmit({ ...this.state });
}
render() {
return (
<React.Fragment>
<form className="entry-form" onSubmit={this.handleSubmit}>
<input
placeholder="Enter a player's name"
className="player-input"
type="text"
onChange={this.handleChange}
/>
<button
type="submit"
className="player-submit"
disabled={this.state.numbersReached} // Does not disable the button as this.state.numbersReached is undefined
>
Add a player
</button>
</form>
</React.Fragment>
)
}
};
export default AddPlayer;
Help is very much appreciated!
The problem you're likely having is that you're not actually using the value of numbersReached in your Redux store. Instead, you're using a numbersReached variable inner to your AddPlayer component' state.
<button
type="submit"
className="player-submit"
disabled={this.state.numbersReached}
>
To use the numbersReached you defined in mapStateToProps, you should access to this.props.numbersReached instead of this.state.numbersReached. The props you inherit from Redux will automatically be updated when the action addPlayer is dispatched and the Redux store changes.
The value for your this.state.numbersReached, however, won't change unless you call setState anywhere in your component. Right now, you're only initializing that value on the constructor:
class AddPlayer extends Component {
constructor(props) {
super(props);
this.state = {
playerName: props.playerName,
numbersReached: props.numbersReached
};
};
}
When you do this, you're creating a new numbersReached variable in your component' state with the value that the numbersReached property in your Redux store had at the time your component is built. As it doesn't have value when the constructor runs, this.state.numbersReached is undefined.
Doing numbersReached: props.numbersReached does not suscribe you to all of the changes to that property; it just assigns numbersReached the value props.numbersReached initially had. That's why, no matter how many players you add, it'll keep been undefined even if players have been succesfully added to your store.
you are duplicating redux state at AddPlayer state. duplicate state is bad practice and should be avoided. secondly, they are different states, redux'state and react component's state. as it is this.state.numbersReached receives the first value on mounting but is not not updated anywehere after.
Also it is weird at your state playerName: props.playerName. first, the fact that you dont have at your redux a piece of state called playerName. Second, the most revelant, it's a component that create players and adds to your playersList. you should better initialize that state as an empty string.
btw, I refactored a little your component to reduce code. if you declare your functions as arrow functions you dont need to bind them at constructor. and you dont need to declare your state at constructor anymore. But if you want to keep the original for consistency, or personal preference go ahead, that's ok also :) .
after all that, your component would look something like:
import React, { Component } from 'react';
class AddPlayer extends Component {
state = {
playerName: ''
};
handleChange = (e) => {
this.setState({ playerName: e.currentTarget.value });
}
handleSubmit = (e) => {
e.preventDefault();
this.props.handleSubmit({ ...this.state });
}
render() {
return (
<React.Fragment>
<form className="entry-form" onSubmit={this.handleSubmit}>
<input
placeholder="Enter a player's name"
className="player-input"
type="text"
onChange={this.handleChange}
/>
<button
type="submit"
className="player-submit"
disabled={this.props.numbersReached}
>
Add a player
</button>
</form>
</React.Fragment>
)
}
};
export default AddPlayer;
a last note, you have numbersReached state, but at your mapStateToProps you set based on a custom function, not on your numbersReached state.
if you want to keep numbersReached state on your redux state, you should handle that logic state.playersList.length >= 10 at your reducers to update properly there, and not at your mapStateToProps. though you could remove from your redux state numbersReached altogether given it's derived from other pieces from your state.

render error while i wanted to get data from server

I have a code that is a component in react which is used Redux to get data from server. this is my very simple component:
class Survey extends React.Component {
constructor(props) {
super(props);
}
componentWillMount() {
this.props.getSurvey(this.props.match.params.uuid);
}
render() {
const { survey } = this.props;
const { name } = survey; // << *********** ERROR **********
return (
<Hoc>
<Card title="Name" bordered={false} style={{ width: 300 }}>
<p>Card content</p>
<p>Card content</p>
<p>Card content</p>
</Card>
</Hoc>
);
}
}
const mapStateToProps = state => {
return {
survey: state.survey.currentSurvey
};
};
const mapDispatchToProps = dispatch => {
return {
getSurvey: uuid => dispatch(surveyGetData(uuid))
};
};
export default withRouter(
connect(
mapStateToProps,
mapDispatchToProps
)(Survey)
);
But when i've used this component it gave me an error which said "survey" is null. i tackled about this issue and i've found out when my getSurvey function in componentWillMount run my render method execute before it goes trough redux action to do process of changing the state and pass it to my component to render and there is my problem.
is there any better way to get through this problem without using a big condition?
Your default value for survey probably is null. So when the component renders the first time 'survey' in const { survey } = this.props; is null. The request to get the survey is async so the response might/will come after the initial render.
One solution might be to have a 'loading' flag on your state. While true you can show a loader when rendering the component. You can set the flag true when you fire the request to get the survey and back to false if the request has finished. At that point the survey should be set and you can render your component as you wish. Of course the request response must be succesfull.
Its because your const { survey } = this.props; is null. For handling this you need some kind of check i.e
const { survey } = this.props;
if(!survey) return null;//ensure component does not render if survey not found
const { name } = survey; //It means we find survey here we can move for next logic

How to re-render a page to get its default values in React js

I have a page that contains a lot of components such as TextFields and comboboxes. When a button, assume Clean, is clicked, I want to refresh the current page to set each component with their default values and also set state to initial state. When I tried window.location.reload(), it refreshes the whole page and user needs to login again.
I tried forceUpdate() but it preserves the current state of the page.
I also tried, it might be absurd I am newbie, to push history as below,
this.props.history.push("/");
this.props.history.push("new-record");
But this didn't work. Page stayed same.
How can I handle this?
try something similar for state reset:
const initialState = { name: 'React' }
class App extends Component {
constructor() {
super();
this.state = initialState
}
resetState=()=>{
this.setState(initialState)
}
render() {
return (
<div>
<Hello name={this.state.name} />
<button
onClick={this.resetState}>reset</button>
</div>
);
}
}
Here is an example with code:
Codepen
There is a local state with defaultInputValue and currentValue:
this.state = {
defaultInputValue: defaultValue,
currentValue: defaultValue
}
setDefault function restore default values:
setDefault = () => {
this.setState({currentValue: this.state.defaultInputValue});
}

React Redux: Delay between component inner content while updating

I have a component that have 2 components inside of it:
MyComp {
render (
html of My Comp..
<Loading show={this.props.isLoading}/>
<ErrorMessage show={this.props.hasError}/>
)
}
When it is receiving data, it shows the Loading.
When the loading is complete, it receive something like:
{
isLoading: false,
hasError: true
}
But in screen, the loading close like 2s before the hasError displays.
Both components are built in the same strategie:
class Loading extends Component {
constructor(props) {
super(props);
this.state = {isLoading : props.show};
}
componentWillReceiveProps(nextProps) {
this.setState({ isLoading: nextProps.show });
}
render() {
if (this.state.isLoading) {
return (
<div className="loading">
<div className="loading-message">
Carregando...
</div>
</div>);
}
return ('');
}
}
export default Loading;
Not exactly an answer for this issue, as i can't be sure where the delay can come from.
But according to your code i would suggest to not use a local state and try to sync it with external props.
This can lead to bugs (maybe related to your issue?) as componentWillReceiveProps can get invoked even when no new props received, beside it is in deprecation process since react V16.2.
Instead i would just read directly from this.props:
class Loading extends Component {
render() {
if (this.props.isLoading) {
return (
<div className="loading">
<div className="loading-message">
Carregando...
</div>
</div>);
}
return ('');
}
}
Again, not sure it is directly related to your issue but it is a better practice.

Error Retrieving Data from React Redux Store and Mapping to Props

I have a React component which is fetching data via API to retrieve an product Object by using the ID passed in as a prop to the component.
I have a React / Redux app and I am fairly new to Redux flow.
I have the Products (array with one Product object) data loading via my Action / Reducer to the store.
I am trying to pass this from state to props using the mapStateToProps pattern.
I am getting the following error when it is rendering the { this.props.product.title }
Uncaught TypeError: Cannot read property '__reactInternalInstance$z9gkwvwuolc' of null
I think its due to it being data thats asynchronous.
What's the best way to solve for this?
Below is my code --
class ProductListItem extends Component {
componentDidMount() {
this.props.dispatch(fetchProduct(this.props.id));
}
render() {
return (
<div>
<h1>{ this.props.product.title }</h1>
</div>
);
}
}
// Actions required to provide data for this component to render in sever side.
ProductListItem.need = [() => { return fetchProduct(this.props.id); }];
// Retrieve data from store as props
function mapStateToProps(state, props) {
return {
product: state.products.data[0],
};
}
ProductListItem.propTypes = {
id: PropTypes.string.isRequired,
dispatch: PropTypes.func.isRequired,
overlay: PropTypes.string,
};
export default connect(mapStateToProps)(ProductListItem);
You need to check if the product exist, you will access the inner data only if it exist. This a common pattern:
class ProductListItem extends Component {
componentDidMount() {
this.props.dispatch(fetchProduct(this.props.id));
}
render() {
const { product } = this.props;
return (
<div>
{ product &&
<h1>{product.title}</h1>
}
</div>
);
}
}
If the product exist, then the component will render the <h1>.
In your redux reducer you can define the default state, set the default state and then you can do some ternary checking
export default function reducer(state={ title : undefined}, action) {
//ternary checking in render() on component
product.title !== undefined ? product.title : undefined
This means, if product.title isn't undefined, then render product.title else undefined.

Resources