Having a weird problem with React props in Firefox. Using Redux and Babel as well.
I'm trying to hide a form, once it has submitted. This works fine on Chrome, but for some reason doesn't work on FF and IE.
So here I have a simple component, a div which houses a form. display class comes from parent component:
class MyForm extends Component {
handleFormSubmit(e) {
// fires an action that sets submitInfo to true
}
render() {
const { display } = this.props;
return (
<div className={display}>
<form onSubmit={ (e) => this.handleFormSubmit(e) }>
// some inputs here
</form>
</div>
);
}
}
When the form submits, an action is fired that sets submitInfo (Redux state) is set to true.
The parent component looks like this:
import { submitInfo, hideForm } from '../actions/main.js'
class App extends Component {
render() {
const {submitInfo, hideForm} = this.props;
var showForm;
if ((submitInfo == true) || (hideForm == true)) {
console.log('evaluating showForm');
showForm = 'hide';
} else {
console.log('evaluating showForm');
showForm = '';
}
return (
<div>
<MyForm display={ 'main-form' + ' ' + showForm } />
</div>
);
}
}
function mapStateToProps(state) {
const { submitInfo, hideForm } = state;
return { submitInfo, hideForm }
}
The parent component checks Redux state for submitInfo = true or hideForm = true. If true then pass value of 'hide' to child component.
Can seem to figure out what is wrong. In Chrome, my console.logs within the parent component seem to be firing every time the state object is re-rendered (ie. whenever an action is fired), but this doesn't happen in Firefox.
State object is being updated correctly, so I can see submitInfo: true and hideForm: true when they're supposed appropriate.
You should use a conditional instead of a class to determine whether to show a component.
The parent component's render method would look something like this:
class App extends Component {
render() {
return (
<div>
{!(this.props.submitInfo && this.props.hideForm) ? <MyForm /> : null}
</div>
);
}
}
Now we can also clean up the child component:
class MyForm extends Component {
handleFormSubmit(e) {
// fires an action that sets submitInfo to true
}
render() {
return (
<div className="main-form">
<form onSubmit={(e) => this.handleFormSubmit(e)}>
...
</form>
</div>
);
}
}
Related
As title.
What would I do to change state inside a callback function if I need to change state after I got some results from the back-end or files ,etc...?
Like this:
var strView="";
var CountIsPrime=function(InputNum){
IsPrime(InputNum,function(Res){
strView=Res;
});
};
export class TrialClass extends React.Component{
state={
DisplayString:strView
};
render(){
return <div>
<label>{strView}</label>
<button onclick={()=>CountIsPrime(Math.floor(Math.Random()*10000))}></button>
</div>
}
}
I am wondering how to change the value inside the label when I call CountIsPrime function?
Issues
strView isn't part of any React component state or props, so no amount of updating it will trigger a rerender to display updated values.
onclick might've been a typo, but it isn't valid.
Solution
Move the CountIsPrime callback definition into the component so it can update the state.
Render the state value into the label.
Use onClick handler.
Code
export class TrialClass extends React.Component{
state = {
strView: '',
};
countInPrime = inputNum => IsPrime(
inputNum,
strView => this.setState({ strView }),
);
render() {
const { strView } = this.state;
return (
<div>
<label>{strView}</label>
<button
type="button"
onClick={() => this.countInPrime(Math.floor(Math.Random() * 10000))}
>
X
</button>
</div>
);
}
}
My react app is a multi-page form. It goes to next page after clicking 'Next'. Currently I have some text that should have a css class when current page is page 1, and when user goes to next page, the css class should be removed for that text (the text is still displayed for all pages).
My actual code is much larger so I'm only posting all the important parts(I think) that are required for this questions.
import ChildComponent from '....';
class Parent extends React.Component {
state = {
page: 1, //default start page
currentPageis1: true,
currentPageis2: false,
currentPageis3: false,
}
change = () => {
const = {page, currentPageis1} = this.state;
this.setState({
page: page + 1 //to go to next page
});
this.setState({
currentPageis1: !currentPageis1
});
}
showPage = () =>{
const {page, currentPageis1} = this.state;
if(page === 1)
return (<ChildComponent
change={this.change}
currentPageis1={currentPageis1}
/>)
}
render(){
return (
<p className={this.currentPageis1 ? '': 'some-css-class'}>Some Text</p>
<form>{this.showPage()}
)
}
}
class ChildComponent extends React.Component {
someFunction = e =>{
e.preventDefault();
this.props.change();
}
render(){
return (
<Button onClick={this.someFunction}>Next</Button>
)
}
}
Currently, when I click Next button, the currentPageis1 updates to false. I checked it using Firefox React extension. But it does not re-render the page. Which means "Some Text" still has the CSS class.
My guess is className={this.currentPageis1 ? '': 'css-class'} in Parent class is only being run once (when the page is first loaded). Do I have to use lifecycle method? How do I make react re-render everytime currentPageis1 is changed?
You are doing <p className={this.currentPageis1 ? '': 'some-css-class'}>Some Text</p>. In order to apply styles to only page 1, you should revert the values in your condition. When currentPageis1 is false '' value is picked up.
Also this.currentPageis1 is wrong. You should use state i.e. this.state.currentPageis1
Working demo
Like this
<p className={this.state.currentPageis1 ? "some-css-class" : ""}>
Some Text
</p>
To get your style to render, you'll need to add the props keyword.
Return Child component inside of Parent and pass the change method as
a prop
Also, updated your setState so you only call it once instead of twice
in the change method
class Parent extends React.Component {
state = {
page: 1, //default start page
currentPageis1: true,
currentPageis2: false,
currentPageis3: false,
}
change = () => {
const = {page, currentPageis1} = this.state;
this.setState({
...this.state,
page: page + 1,
currentPageis1: !currentPageis1
});
}
render(){
return (
<div>
<p className={this.props.currentPageis1 ? '': 'some-css-class'}>Some Text</p>
<Child change={this.change} />
</div>
)
}
}
I've have an component "A" with a button. When the user press the button I'm showing a modal(react-responsive-modal) with bunch of filed and an update button. When the user presses the update button on the modal I want to reload the component "A" with the updated data.
I tried redirecting using this.props.history.push('dashboard/componentA') but it didn't work. Then I tried redirecting to the dashboard and again redirecting to the component like this
this.props.history.push('/dashboard');
this.props.history.push('/dashboard/componentA');
It worked but I'm not seeing any loader that I've used on 'componentWillMount' and the component just freezes up. I couldn't scroll up or down.
Try not to use the browser history as a way to update react (as much as you can). React is designed to re-render components when the props or state for that component change. As an example, this should trigger an update in ComponentA without needing to update the browser's history:
class ComponentA extends Component {
handleModalClick = (event) => {
this.setState({
componentData: event.data,
});
}
render() {
return (
<ReactModal onClick={this.handleClick} />
)
}
}
EDIT: Updated to show a data fetching parent component:
class DataFetcher extends Component {
saveAndFetchData = (saveData) => {
FetchDataPromise(saveData).then((updatedData) => {
this.setState({ data: updatedData });
}
}
render() {
const { data } = this.state;
return (
<div>
<ComponentA data={data} />
<ReactModalComponent handleClick={saveAndFetchData} />
</div>
);
}
}
class ComponentA extends Component {
render() {
const { data } = this.props;
return (
<div>
...render data...
</div>
)
}
}
I've created a component with React using Redux where it takes two renders before the state is mapped to the props.
with this code,
'user.private' on first render is null and on the second render, it's false
because of that, loading the page flickers between showing 'login' for a second before showing hidden content
I'd like to show the login text by default, but I don't actually want it to display if the user's private field is set to false.
class Content extends React.Component {
render() {
const { user } = this.props;
let show = false;
if (user.private === false) show = true
return (
<section>
{
show
? <p>hidden content</p>
: <p>login</p>
}
</section>
)
}
}
const mapStateToProps = state => ({
user: state.store.user
});
export default connect(mapStateToProps, {})(Content)
You can use switch to handle the null case (you can either show a loader or just render nothing by returning null)
I am using the component state to illustrate the idea, but you can apply it to your redux connected Component
class App extends React.Component {
state = {
user : {
private : null
}
}
componentDidMount () {
setTimeout(() => {
this.setState(() => {
return {
user : {
private : true
}
}
});
}, 2000);
}
renderContent () {
const { user } = this.state;
switch (user.private) {
case null : return <span>Loading...</span>
case false : return <p>login</p>
default : return <p>hidden content</p>
}
}
render () {
return (
<div>
{this.renderContent()}
</div>
);
}
}
ReactDOM.render(
<App/>,
document.querySelector('#root')
);
demo
Assumming user is initially undefined or null, you can check if user and/or its property private is defined before showing anything:
if (this.props.user == null || this.props.user.private) {
show = false;
}
The double equals null will make the condition true if the value of this.props.user is undefined or null. You could also have used !this.props.user.
In case you made your initial value for user be {} even before getting it, then you would have to do:
if (this.props.user.private == null || this.props.user.private) {
show = false;
}
I'm new to React and am running into the same problem a few times. In this particular situation, I'm trying to get an option in a select dropdown to update when I update a text input.
I have a parent, App, with the state attribute "directions", which is an array. This gets passed as a property to a child, GridSelector, which creates the text field and dropdown. When the text field is changed, a function triggers to update the parent state. This in turn causes the GridSelector property to update. However, the dropdown values, which are originally generated from that GridSelector property, do not re-render to reflect the new property value.
I'm trying to figure out the most React-ful way to do this and similar manuevers. In the past, I've set a state in the child component, but I think I've also read that is not proper.
My working site is at amaxalaus.bigriverwebdesign.com
Here's the pertinent code from each file:
App.js
class App extends React.Component {
constructor(props){
super(props);
this.state = {
directions: [],
dataRouteDirections: '/wp-json/wp/v2/directions',
currentDirectionsIndex: 0
}
this.addImageToGrid = this.addImageToGrid.bind(this);
this.changeTitle=this.changeTitle.bind(this);
}
componentDidMount(){
fetch(this.state.dataRouteDirections)
.then(data => data=data.json())
.then(data => this.setState({directions:data}));
}
addImageToGrid(image) {
this.refs.grid.onAddItem(image); //passes image add trigger from parent to child
}
createNewDirections(){
var directions= this.state.directions;
var index = directions.length;
var lastDirections = directions[directions.length-1];
var emptyDirections= {"id":0,"acf":{}};
emptyDirections.acf.grid="[]";
emptyDirections.acf.layout="[]";
emptyDirections.title={};
emptyDirections.title.rendered="New Directions";
if (lastDirections.id!==0 ) { ///checks if last entry is already blank
this.setState({
directions: directions.concat(emptyDirections), //adds empty directions to end and updates currentdirections
currentDirectionsIndex: index
});
}
}
changeTitle(newTitle){
var currentDirections = this.state.directions[this.state.currentDirectionsIndex];
currentDirections.title.rendered = newTitle;
}
render() {
var has_loaded; //has_loaded was added to prevent double rendering during loading of data from WP
this.state.directions.length > 0 ? has_loaded = 1 : has_loaded = 0;
if (has_loaded ) {
/* const currentGrid = this.state.directions;*/
return ( //dummy frame helpful for preventing redirect on form submit
<div>
<div className="fullWidth alignCenter container">
<GridSelector
directions={this.state.directions}
currentDirectionsIndex={this.state.currentDirectionsIndex}
changeTitle={this.changeTitle}
/>
</div>
<Grid ref="grid"
currentGrid={this.state.directions[this.state.currentDirectionsIndex]}
/>
<ImageAdd addImageToGrid={this.addImageToGrid}/>
<div className="fullWidth alignCenter container">
<button onClick={this.createNewDirections.bind(this)}> Create New Directions </button>
</div>
</div>
)
} else {
return(
<div></div>
)
}
}
}
GridSelector.js
class GridSelector extends React.Component {
constructor(props) {
super(props);
var currentDirections = this.props.directions[this.props.currentDirectionsIndex];
this.state = {
currentTitle:currentDirections.title.rendered
}
}
createOption(direction) {
if (direction.title) {
return(
<option key={direction.id}>{direction.title.rendered}</option>
)
} else {
return(
<option></option>
)
}
}
handleChangeEvent(val) {
this.props.changeTitle(val); //triggers parent to update state
}
render() {
return(
<div>
<select name='directions_select'>
{this.props.directions.map(direction => this.createOption(direction))}
</select>
<div className="fullWidth" >
<input
onChange={(e)=>this.handleChangeEvent(e.target.value)}
placeholder={this.state.currentTitle}
id="directionsTitle"
/>
</div>
</div>
)
}
}
You made a very common beginner mistake. In React state should be handled as an immutable object. You're changing the state directly, so there's no way for React to know what has changed. You should use this.setState.
Change:
changeTitle(newTitle){
var currentDirections = this.state.directions[this.state.currentDirectionsIndex];
currentDirections.title.rendered = newTitle;
}
To something like:
changeTitle(newTitle){
this.setState(({directions,currentDirectionsIndex}) => ({
directions: directions.map((direction,index)=>
index===currentDirectionsIndex? ({...direction,title:{rendered:newTitle}}):direction
})