React rendering content before props is properly mapped / set - reactjs

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

Related

Changing state in React JS is not re-rendering parent

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

Calling props from a container

I am a little confused on the idea of using props in the context I am using for my React app. In my component, I need to check if the value of a certain prop (props.companyCode) matches a certain string, and only then will it print out a <p> of what I need. Below is what I have for calling the prop in the component:
Components/CompanyContact.jsx
class CompanyContact extends React.Component {
help() {
if (this.props.companyInfoList.companyCode === '1234') {
return <p>something</p>;
}
return <p>somethingelse</p>;
}
render() {
const help = this.help();
return (
<div>
{help};
</div>
)}}
export default CompanyContact;
And this is what I have for the container:
Container/InfoContainer.jsx
class InfoContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
companyInfoList: null,
};
}
async componentWillMount() {
const companyInfoCachedData = CachingService.getData('companyInfoList');
if (companyInfoCachedData) {
this.setState({ companyInfoList: companyInfoCachedData });
return;
}
}
async getCompanyInfo(accessToken) {
try {
const companyProfileResponse = await requestAWSGet('api/company-profile', undefined, accessToken);
CachingService.setData('companyInfoList', companyProfileResponse);
this.setState({ companyInfoList: companyProfileResponse });
} catch (err) {
throw err;
}
}
render() {
return (
<CompanyContact companyInfoList={this.state.companyInfoList} />
);
}
}
export default InfoContainer;
Nothing is returned when I run the application and I believe it's because I'm not calling the prop correctly in my component but I am unsure as to how to go about fixing it. I'm fairly new to working with props so still trying to get my bearings.
I'm assuming you are getting an error somewhere because of this not having props and this.props.companyInfoList.companyCode trying to access a property on a non object. this.props.companyInfoList is initially set to null so accessing a property on it will break.
A few strategies to fix the problem:
Default it to an empty object
this.state = {
companyInfoList: {},
}
Block the rendering of the component until it has a value:
if (this.state.companyInfoList) {
return (
<CompanyContact companyInfoList={this.state.companyInfoList} />
);
} else {
return null;
}
Check that the prop is an object and has the key companyCode on it:
if (this.props.companyInfoList &&
this.props.companyInfoList.companyCode &&
this.props.companyInfoList.companyCode === '1234') {
In addition, this will be in the wrong context and the changes above will most likely no be enough. Try changing to an arrow function like this:
help = () => {
// your code here
}
I would personally refactor that component logic and directly use the prop value inside the render method like:
class CompanyContact extends React.Component {
render() {
const { companyInfoList } = this.props;
return companyInfoList && companyInfoList.companyCode === '1234' ? (
<p>something</p>
) : (
<p>somethingelse</p>
)
}
}
export default CompanyContact;

Called componentDidMount twice

I have a small react app. In App.js I have layout Sidenav and Content area. The side nav is shown on some page and hid from others. When I go to some components with sidenav, sidenav flag is set by redux and render the component again, in the componentDidMount I have api call, and it is executed twice.
class App extends Component {
renderSidebar = () => {
const {showNav} = this.props;
return showNav ? (
<TwoColumns>
<Sidenav/>
</TwoColumns>) : null;
};
render() {
const {showNav} = this.props;
const Column = showNav ? TenColumns : FullColumn;
return (
<Row spacing={0}>
{this.renderSidebar()}
<Column>
<Route exact path="/measurements/:id/:token/:locale/measure"
component={MeasurementPage}/>
</Column>
</Row>
)
}
}
const mapStateToProps = state => ({
showNav: state.sidenav.showNav
});
export default connect(mapStateToProps)(App);
I tried to use shouldComponentUpdate to prevent the second API call
class MeasurementPage extends Component {
constructor(props) {
// This update the redux "showNav" flag and re-render the component
props.toggleSidenav(false);
}
shouldComponentUpdate(nextProps) {
return !nextProps.showNav === this.props.showNav;
}
componentDidMount() {
// This is executed twice and made 2 api calls
this.props.getMeasurement(params);
}
render() {
return <h1>Some content here</h1>;
}
}
const mapStateToProps = state => ({
showNav: state.sidenav.showNav
});
export default connect(mapStateToProps)(MeasurementPage);
Did someone struggle from this state update and how manage to solve it?
This props.toggleSidenav(false) might cause side effect to your component lifecycle. We use to do this kind of stuff inside componentWillMount and it has been depreciated/removed for a reason :). I will suggest you move it inside componentDidMount
class MeasurementPage extends Component {
constructor(props) {
// This update the redux "showNav" flag and re-render the component
// props.toggleSidenav(false); // remove this
}
shouldComponentUpdate(nextProps) {
return nextProps.showNav !== this.props.showNav;
}
componentDidMount() {
if(this.props.showNav){ //the if check might not necessary
this.props.toggleSidenav(false);
this.props.getMeasurement(params);
}
}
render() {
return <h1>Some content here</h1>;
}
}
The comparison should be
shouldComponentUpdate(nextProps) {
return !(nextProps.showNav === this.props.showNav)
}
The problem is that !nextProps.showNav negate showNav value instead of negating the role expression value, and that is why you need an isolation operator.
It's No call twice anymore.
componentDidMount() {
if (this.first) return; this.first = true;
this.props.getMeasurement(params);
}

Why is setState being reset?

I'm working on a community site where users can share and comment on websites (here it is)[https://beta.getkelvin.com/]. Websites can be filtered by category, but when a user goes into a specific page then backs out, the filter is removed.
Here's the process in steps:
User selects filter from drop down list, and this.state.topicSelected reflects the new value
User clicks a link to see a show page of an instance (a summary of a website), this.state.topicSelected reflects the correct value
User goes back to main page, and this.state.topicSelected is reverted back to 0
Instead of reverting back to 0, I want the value to still apply so the filter remains on the same category that the user selected before.
The problem seems to be that getInitialState is resetting the value of this.state.topicSelected back to 0 (as it's written in the code). When I try to put a dynamic value in 0's place, I get an undefined error.
Here's the getInitialState code:
var SitesArea = React.createClass({
getInitialState: function () {
return {
sortSelected: "most-recent",
topicSelected: 0 // need to overwrite with value of this.state.topicSelected when user selects filter
// I removed other attributes to clean it up for your sake
}
}
On here's the event:
onTopicClick: function (e) {
e.preventDefault();
this.setState({topicSelected: Number(e.target.value)});
if (Number(e.target.value) == 0) {
if (this.state.sortSelected == 'highest-score') {
this.setState({sites: []}, function () {
this.setState({sites: this.state.score});
});
} else if (this.state.sortSelected == 'most-remarked') {
this.setState({sites: []}, function () {
this.setState({sites: this.state.remarkable});
});
} else if (this.state.sortSelected == 'most-visited') {
this.setState({sites: []}, function () {
this.setState({sites: this.state.visited});
});
} else if (this.state.sortSelected == 'most-recent') {
this.setState({sites: []}, function () {
this.setState({sites: this.state.recent});
});
}
} else {
this.getSites(this.state.sortSelected, Number(e.target.value));
this.setState({sites: []}, function () {
this.setState({sites: this.state.filter_sites});
});
}
And lastly, the dropdown menu:
<select
value={this.state.topicSelected}
onChange={this.onTopicClick}
className="sort"
data-element>
{
// Add this option in the .then() when populating siteCategories()
[<option key='0'value='0'>Topics</option>].concat(
this.state.topics.map(function (topic) {
return (<option
key={topic.id}
value={topic.id}>{topic.name}</option>);
}))
}
How do I get it so that this.state.topicSelected doesn't get reset when a user goes back to the main page?
I think your main page is getting unmounted (destroyed) when the user navigates from the main page to the summary page. React creates a brand new instance of the main page component when they return. That reconstruction initializes the selected topic back to 0.
Here is a codepen that shows what might be happening. Foo == your main page, Bar == summary page. Click Increment topic a couple times, then navigate from Foo to Bar and back again. Console logs show that the Foo component gets unmounted when you navigate away, and reconstructed on return.
Note You seem to be using an older version of react, as evidenced by the presence of getInitialState and React.createClass. My pen follows the more modern approach of initializing state in the class constructor.
To solve the problem, you have to save that state outside the main component in something that isn't getting deleted and re-created as they navigate. Here are some choices for doing that
Expose an onTopicSelected event from your main page. The parent of the main page would pass in a function handler as a prop to hook that event. The handler should save the selected topic in the component state of the parent. This is kind of messy solution because the parent usually should not know or care about the internal state of its children
Stuff the selected topic into something that isn't react, like window.props. This is an ugly idea as well.
Learn about redux and plug it into your app.
Redux is the cleanest way to store this state, but it would require a bit of learning. I have implemented the first solution in this codepen if you want to see what it would look like.
The original codepen showing the problem is pasted below as a snippet. Switch to Full page mode if you try to run it here.
//jshint esnext:true
const Header = (props) => {
const {selectedPage, onNavigationChange} = props;
const disableFoo = selectedPage == 'foo'
const disableBar = selectedPage == 'bar';
return (
<div>
<h1>Header Component : {selectedPage}</h1>
<button disabled={disableFoo} onClick={() => onNavigationChange('foo')}>Foo page</button>
<button disabled={disableBar} onClick={() => onNavigationChange('bar')}>Bar page</button>
<hr/>
</div>
);
};
class Foo extends React.Component {
constructor(props) {
console.log('Foo constructor');
super(props);
this.state = {
topicSelected: 0
};
this.incrementTopic = this.incrementTopic.bind(this);
}
incrementTopic() {
const {topicSelected} = this.state
const newTopic = topicSelected + 1
console.log(`incrementing topic: old=${topicSelected} new=${newTopic}`)
this.setState({
topicSelected: newTopic
})
}
render() {
console.log('Foo::render');
return (<div>
<h2>The Foo content page : topicSelected={this.state.topicSelected}</h2>
<button onClick={this.incrementTopic}>Increment topic</button>
</div>
);
}
componentWillMount() {
console.log('Foo::componentWillMount');
}
componentDidMount() {
console.log('Foo::componentDidMount');
}
componentWillUnmount() {
console.log('Foo::componentWillUnmount');
}
componentWillUpdate() {
console.log('Foo::componentWillUpdate');
}
componentDidUpdate() {
console.log('Foo::componentDidUpdate');
}
}
const Bar = (props) => {
console.log('Bar::render');
return <h2>The Bar content page</h2>
}
const Body = (props) => {
console.log('Body::render');
const {selectedPage} = props;
if (selectedPage == 'foo') {
return <Foo/>;
} else if (selectedPage == 'bar') {
return <Bar/>
}
};
class Application extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedPage: 'foo'
};
}
render() {
console.log('Application::render');
const {selectedPage} = this.state;
const navigationChange = (nextPage) => {
this.setState({
selectedPage: nextPage
})
}
return (
<div>
<Header selectedPage={selectedPage} onNavigationChange={navigationChange}/>
<Body selectedPage={selectedPage}/>
</div>
);
}
}
ReactDOM.render(<Application/>,
document.getElementById('main')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="main"></div>

React prop not transferring in Firefox

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

Resources