Conditional rendering issue (ReactJS) - javascript

I'm having an issue rendering proper content in my component. Component has access to all the necessary store and props, I console.log to confirm that and yet it fails to render content.
I setup a store like that:
const levelDefaultState = {
level: "second"}
And my component where I want to render things depending on the current state looks like this:
import React from 'react';
import { connect } from 'react-redux'
import LevelTwo from './level2/level2'
class LevelRenderer extends React.Component {
renderLevel(){
let currentLevel = null;
if (this.props.level === "second") {
currentLevel = <LevelTwo level={this.props.level} />
} else {
currentLevel = 'No level'
}
return currentLevel;
}
render() {
console.log('store', this.props.level)
let renderLevelConst = (this.props.level) ? this.renderLevel() : notRendered()
function notRendered() {
return 'Not rendered yet'
}
return (
<div>
{renderLevelConst}
</div>
)
}
}
const mapStateToProps = (state) => {
return {
level: state.level
}
}
export default connect(mapStateToProps)(LevelRenderer);
And even though the state is "second" all the time all it renders is "no level" string. Tried to setup the initial state to "first" then changing it to "second" with setTimeout, no changes whatsoever.
What am I doing wrong?

Try:
if (this.props.level.level === "second") {} in renderLevel
Since this.props.level = {level: "second", sublevel: "1.splash"} you have to call this.props.level.level to obtain the level value.

Related

Filter data in react based on parent prop

How do I filter data in a component based on a parent prop?
My data to compare to contains something like:
{
"id": "5a7847508f9337cf77712128",
"index": 0,
"projectName": "Ovolo",
"location": "Stoddard Place, Riverton, New York, 3571"
},
{
"id": "5a7847503101a8ef7f7d3c30",
"index": 1,
"projectName": "Isonus",
"location": "Elliott Walk, Magnolia, Minnesota, 4488"
}...
My component looks something like this:
import React, {Component} from 'react';
import data from "../data/projects.json";
const ProjectTitle = data.filter((projects, props) => {
if (projects.id === props.project_id )
var title = projects.projectName;
return title;
});
class ProjectName extends Component {
constructor() {
super();
this.state = {
projectTitle: ProjectTitle
}
}
componentDidMount() {
this.setState({
projectTitle: ProjectTitle
});
}
render() {
return (
<h4 className="projectName">
{ this.state.projectTitle }
</h4>
);
}
}
export default ProjectName;
Basically, I want to match props.project_id from the parent component with the first project from my data that matches the id, so I can display the projectName using the component. Currently, the one that I tried does not return the name of the project. I am new to this so I need help and guidance, maybe an example or a reference may help.
Basically you wants to watch for different project_id sent from the parent and depending on which want to filter the data, as far as i understood.
One thing you can do is use componentWillReceiveProps lifecycle to watch for new props. and can also componentDidMount lifecycle to set the projecttitle.
you need to change the projectTitle function also
const ProjectTitle = (projectId) => {
const project = data.filter((projects) => {
if (projects.id === projectId ) {
return true;
}
return false
});
if(project) {
return projects.projectName;
}
return project
}
componentWillReceiveProps(nextProps) {
const {project_id} = nextProps
const newProjectTitle = ProjectTitle(project_id)
this.setState({
projectTitle: newProjectTitle,
})
}
componentDidMount() {
const {project_id} = this.props
const newProjectTitle = ProjectTitle(project_id)
this.setState({
projectTitle: newProjectTitle,
})
}
By adding componentWIllReceiveprops and componentDidMount you can
dynamically change the project name whenever new project_id is passed
as a props. you can also use the constructor function to initialize the state like this
constructor(props) {
super(props)
this.state = {
projectTitle: ProjectTitle(props.project_id),
}
}
Refer this lifecycle methods in react for more reference
Not sure why it isn't working exactly but that ProjectTitle that you create outside the class doesn't ring well with me. I think that you're assigning it to the state and that's not working as expected. You should do the filter (or find) operation in the componentWillMount function of the React lifecycle . After you find the project you do the setState and it should work.
class ProjectName extends Component {
constructor() {
super();
this.state = {
projectTitle: ''
}
}
componentWillMount() {
const project = data.find(project => project.id === this.props.project_id);
if (project) {
this.setState({
projectTitle: project.projectName
});
}
}
render() {
return (
<h4 className="projectName">
{this.state.projectTitle}
</h4>
);
}
}
export default ProjectName;
Assuming props.project_id is a ProjectName's prop the easiest solution is to move the filter to the render function. Also, if you want to find one record use find not filter.
render() {
const project = data.find(( {id} ) => id === this.props.project_id)
return (
<h4 className="projectName">
{ project && project.title }
</h4>
);
}
You could do as other suggested and set the state during the component's creation, but then you'd have to do the same in componentWillReceiveProps, this creates a lot of unnecessary complexity.
If performance is an issue (it generally isn't) and you want to find the project only once per id, use a memoized function and keep the logic in the render function. This is a lot simpler and has the same effect.

React HOC class returns nothing

I am facing a problem I can't figure out. For a project we use React to generate a layout from JSON input using the following code (simplified):
function generateComponents(children, params) {
let comps = [];
if (!children || children && children.length === 0) {
return [];
}
forEach(children, (comp) => {
let compName = comp.component;
let createdComp;
switch (compName) {
case 'test1':
createdComp = TestOne(Object.assign({}, comp, params));
break;
case 'test2':
createdComp = TestTwo(Object.assign({}, comp, params));
break;
}
comps.push(createdComp)
}
}
return comps.length === 1 ? comps[0] : comps;
}
This works well and the layout is generated correctly. We wanted to take this a step further and wrap the createdComp in a Higher Order Component. We implemented that in the following way:
function generateComponents(children, params) {
// see above for implementation
let component;
if (condition)
component = testingHOC(createdComp);
else
component = createdComp
comps.push(component);
}
// TestingHOC.js
export function testingHoc(WrappedComponent) {
console.log('wrapped')
return class TestingHoc extends Component {
render() {
console.log('props TestingHOC', this.props);
return ( <WrappedComponent { ...this.props} />);
}
}
};
This broke our component generation. The code returns nothing. The only thing that gets logged is the console.log('wrapped'), the render function of the class is never called. What are we missing here?
EDIT:
Render method of the render class:
render() {
const children = this.state.children;
const {params} = this.props;
const arrChildren = isArray(children) ? children : [children];
let comps = generateComponents(arrChildren, params || {});
if (isArray(comps)) {
return (
<ViewComponent>
{comps}
</ViewComponent>
);
} else {
return comps;
}
}
EDIT 2:
Console.log of {comps} with the testingHoc
Console.log of {comps} without the testingHoc
Edit 3
Added the code for ViewComponent:
import React from 'react';
const ViewComponent = (props) => (
<div {...props}/>
);
export default ViewComponent;
The issue you are facing is because of the inherent difference between a React element and a React component.
When you are not using the HOC, you are creating a React element which can be seen by the first console.log image. This is the output after reconciliation has occurred.
When you use the HOC, your HOC returns a component which shows up as the test(props) function in your second console.log image.
To have the same functionality with your HOC enhanced components, you need to change the code in generateComponents functions to
if (condition){
let Comp = testingHOC(createdComp);
component = <Comp/>;
}
Try
...
return (
<ViewComponent>
{comps.map((Comp) => <Comp />)}
</ViewComponent>
);
...
or
...
return (
<ViewComponent>
{comps.map((comp) => comp())}
</ViewComponent>
);
...

Child Component Not Getting New Data When Redux Updated By Parent Component

I have looked at other questions that seemingly had a similar issue, but none of the accepted answers have solved my issue. I am attempting to fetch new names and load them into child component when redux is updated with new IDs.
When I use only redux and no state (as I would prefer), the new IDs do not get passed along to the child component and the names do not load at all
Alternatively, I have tried using state for the names in the child component (as you can see in the commented text below). However ... Oddly enough, every time the IDs are changed, the component loads the names based on the previous IDs rather than the current IDs.
Redux
const initialState = {
objectOfIds: {"someID":"someID", "aDifferentID":"aDifferentID"}, // I know this format may seem redundant and odd, but I have to keep it this way
arrayOfNames: ["John Doe", "Jenny Smith"]
}
Parent Compoenent
// React
import React from 'react';
import firebase from 'firebase';
// Components
import ListOfNames from './ListOfNames';
// Redux
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {set} from './../actions/index.js';
class ParentComponent extends React.Component {
constructor(props) {
super(props);
this.changeIDs = this.changeIDs.bind(this);
}
changeIDs() {
this.props.set("objectOfIds",{"aNewID":"aNewID","someOtherID":"someOtherID","anotherID":"anotherID"});
}
render (
return (
<div>
<h2>Parent Component</h2>
<button onClick={this.changeIDs}>Change Data</button>
<ListOfNames objectOfIds={this.props.reduxData.objectOfIds}/>
</div>
)
)
}
function mapStateToProps(state) {
return {
reduxData: state.reduxData
};
}
function matchDispatchToProps(dispatch) {
return bindActionCreators({
set: set
}, dispatch)
}
export default connect(mapStateToProps, matchDispatchToProps)(ParentComponent);
Child Compoenent
// React
import React from 'react';
import firebase from 'firebase';
// Redux
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {set} from './../actions/index.js';
// Firebase Database
var databaseRef;
class ListOfNames extends React.Component {
constructor(props) {
super(props);
this.state= {
arrayOfNames: []
}
this.fetchNamesForIds = this.fetchNamesForIds.bind(this);
this.add = this.add.bind(this);
}
componentDidMount() {
console.log("componentDidMount triggering...");
firebase.auth().onAuthStateChanged(function (user) {
if (!user) {
console.log("no user authenticated");
}
databaseRef = firebase.database().ref('/people/' + user.uid);
this.fetchNamesForIds(this.props.reduxData.objectOfIds);
})
}
// I have tried making the fetch in componentWillReceiveProps so that the function would run anytime the IDs were updated in redux, but "this.props.objectOfIds" and "this.props.reduxData.objectOfIds"
componentWillReceiveProps() {
console.log("componentWillReceiveProps triggering...");
console.log("this.props.objectOfIds");
console.log(this.props.objectOfIds);
console.log("this.props.reduxData.objectOfIds");
console.log(this.props.reduxData.objectOfIds);
this.fetchNamesForIds(this.props.reduxData.objectOfIds);
// Note: I have also tried: this.fetchNamesForIds(this.props.objectOfIds); so that the data is passed in from the parent
}
// fetched the names for the associated IDs
fetchNamesForIds(personIds) {
if (personIds === [] || personIds === undefined || personIds === null) {
ALTERNATIVE TO LINE ABOVE
I would prefer to store the data in redux so that it is accessible to other components, but doing this did allow the data to load, but it loads with a lag (i.e. when I change the IDs, it loads the names associated to the previous IDs)
// this.setState({
// arrayOfNames: []
// });
this.props.set("arrayOfNames", []);
return
}
var arrayOfNames = [];
// loop through person and set each value into the arrayOfNames array
Object.keys(IDs).map(function(person, index) {
console.log("person = " + person);
console.log("index = " + index);
// get names associated with the ids obtained
var name = ''
databaseRef.child('people').child(person).limitToFirst(1).on("value", function(snapshot) {
var firstName = snapshot.child('firstName').val()
var lastName = snapshot.child('firstName').val()
name = firstName + " " + lastName
console.log("name = " + name);
arrayOfNames.push(name);
console.log("arrayOfNames = " + arrayOfNames);
this.props.set("arrayOfNames", arrayOfNames);
ALTERNATIVE TO LINE ABOVE
I would prefer to store the data in redux so that it is accessible to other components, but doing this did allow the data to load, but it loads with a lag (i.e. when I change the IDs, it loads the names associated to the previous IDs)
// this.setState({
// arrayOfNames: arrayOfNames
// });
}.bind(this));
}.bind(this));
}
render() {
return(
(this.props.user.arrayOfNames === [] || this.props.user.arrayOfNames === undefined || this.props.user.arrayOfNames === null || this.props.user.arrayOfNames.length < 1)
? <span>no people selected</span>
: <div>
<h5>List of People</h5>
{this.props.user.arrayOfNames.map((name, index) => {
return (
<h5>{name}</h5>
)
})}
</div>
)
}
}
ListOfNames.propsTypes = {
objectOfIds: React.PropTypes.Object
};
function mapStateToProps(state) {
return {
reduxData: state.reduxData
};
}
function matchDispatchToProps(dispatch) {
return bindActionCreators({
set: set
}, dispatch)
}
export default connect(mapStateToProps, matchDispatchToProps)(ListOfNames);
Similar Questions:
https://github.com/gaearon/redux-thunk/issues/80
React Native Child Component Not Updated when redux state changes
update child component when parent component changes
Does anyone understand how I can get my component to load the data based on the current IDs in redux?
Probably because the object keys are changed but not the object reference.
A hacky solution would be to call this.forceUpdate() to update the component after the change:)
I had a similar issue where I was loading a child component multiple times on one page and despite passing in what I thought was a unique ID it would only reference the first ID. I know this isn't exactly the situation you have but this will allow you to have a unique object key AND a unique object reference which will hopefully fix your issue.
This is the package I used for this: https://www.npmjs.com/package/react-html-id. When you import the package you need to have curly brackets.
import { enableUniqueIds } from 'react-html-id'
The rest is explained on npm.
Tip: you don't need to bind your functions if you use the new javascript syntax.
this.add = this.add.bind(this); will be solved by writting the add method like:
add = () => {
};
The issue is the Child Component componentWillReceiveProps. You are not using the new props that are propagated to this component. componentWillReceiveProps is called with nextProps, which contains the updated props.
Use this in your child component
componentWillReceiveProps(nextProps) {
console.log("componentWillReceiveProps triggering...");
console.log("nextProps.objectOfIds ", nextProps.objectOfIds);
console.log("nextProps.reduxData.objectOfIds ", nextProps.reduxData.objectOfIds);
this.fetchNamesForIds(nextProps.reduxData.objectOfIds);
}

React does not render recursive reactive components

Given this component :
import React, { Component } from 'react';
import TrackerReact from 'meteor/ultimatejs:tracker-react';
export default class SubscriptionView extends TrackerReact(Component) {
constructor(props) {
super(props);
let params = props.params || [];
if (!Array.isArray(params)) {
params = [params];
}
this.state = {
subscription: {
collection: Meteor.subscribe(props.subscription, ...params)
}
};
}
componentWillUnmount() {
this.state.subscription.collection.stop();
}
render() {
let loaded = this.state.subscription.collection.ready();
if (!loaded) {
return (
<section className="subscription-view">
<h3>Loading...</h3>
</section>
);
}
return (
<section className="subscription-view">
{ this.props.children }
</section>
);
}
};
And another component :
import SubscriptionView from './SubscriptionView.jsx';
export const Foo = () => (
<SubscriptionView subscription="allFoo">
<SubscriptionView subscription="singleBar" params={ 123 }>
<div>Rendered!</div>
</SubscriptionView>
</SubscriptionView>
);
The first Subscription is re-rendered when the data is available, however the second one is rendered only once and nothing more. If I place a console.log(this.props.subscription, ready); inside the render function of SubscriptionView, I see
allFoo false
allFoo true
singleBar false
and that's it.
On the server side, both publish methods are
Meteor.publish('allFoo', function () {
console.log("Subscribing foos");
return Foos.find();
});
Meteor.publish('singleBar', function (id) {
console.log("Subscribing bar", id);
return Bars.find({ _id: id });
});
Both of the publish methods are being called.
Why isn't the second SubscriptionView reactive?
* Solution *
This is based on alexi2's comment :
import React, { Component } from 'react';
import TrackerReact from 'meteor/ultimatejs:tracker-react';
export default class SubscriptionLoader extends TrackerReact(Component) {
constructor(props) {
super(props);
let params = props.params || [];
if (!Array.isArray(params)) {
params = [params];
}
this.state = {
done: false,
subscription: {
collection: Meteor.subscribe(props.subscription, ...params)
}
};
}
componentWillUnmount() {
this.state.subscription.collection.stop();
}
componentDidUpdate() {
if (!this.state.done) {
this.setState({ done: true });
this.props.onReady && this.props.onReady();
}
}
render() {
let loaded = this.state.subscription.collection.ready();
if (!loaded) {
return (
<div>Loading...</div>
);
}
return null;
}
};
Then, inside the parent component's render method :
<section className="inventory-item-view">
<SubscriptionLoader subscription='singleBar' params={ this.props.id } onReady={ this.setReady.bind(this, 'barReady') } />
<SubscriptionLoader subscription='allFoos' onReady={ this.setReady.bind(this, 'foosReady') } />
{ content }
</section>
Where setReady merely sets the component's state, and content has a value only if this.state.barReady && this.state.foosReady is true.
It works!
Try separating out your SubscriptionView Components like this:
import SubscriptionView from './SubscriptionView.jsx';
export const Foo = () => (
<div>
<SubscriptionView subscription="singleBar" params={ 123 }>
<div>Rendered!</div>
</SubscriptionView>
<SubscriptionView subscription="allFoo">
<div>Rendered Again!</div>
</SubscriptionView>
</div>
);
Edit from comments conversation
Not sure if I am on the right track but you could build Foo as a 'smart' component that passes props to each SubscriptionView as required, and then use Foo as a reusable component.
Let's say that what I need to render is FooBarForm, which requires both Foos and Bars to be registered, in that specific use case. How would you do that?
You could create Components Foos and Bars that took props as required and create a parent component FooBarForm that contained those Components and passed the necessary data.
FooBarForm would handle the state and as that changed pass it to the props of its child components.
Now state is being centrally managed by the parent component, and the child components render using props passed from the parent.
The child components would re-render as their props changed depending on whether the state being passed from the parent component had changed.

Avoid recalculating variable on every render in React

I have a component ParentToDataDisplayingComponent that is creating a few lookups to help format data for a child component based on data in a redux store accessed by the parent of ParentToDataDisplayingComponent.
I am getting some lagging on the components rerendering, where the changing state has not affected this.props.dataOne or this.props.dataTwo - the data in these lookups is guaranteed the same as last render, but the data in props is not guaranteed to be the available (loaded from the backend) when the component mounts. mapPropsToDisplayFormat() is only called after all of the data passed in through the props is available.
I would like to declare the lookup variables once, and avoid re-keyBy()ing on every re-render.
Is there a way to do this inside the ParentToDataDisplayingComponent component?
export default class ParentToDataDisplayingComponent extends Component {
...
mapPropsToDisplayFormat() {
const lookupOne = _(this.props.dataOne).keyBy('someAttr').value();
const lookupTwo = _(this.props.dataTwo).keyBy('someAttr').value();
toReturn = this.props.dataThree.map(data =>
... // use those lookups to build returnObject
);
return toReturn;
}
hasAllDataLoaded() {
const allThere = ... // checks if all data in props is available
return allThere //true or false
}
render() {
return (
<div>
<DataDisplayingComponent
data={this.hasAllDataLoaded() ? this.mapPropsToDisplayFormat() : "data loading"}
/>
</div>
);
}
}
Save the result of all data loading to the component's state.
export default class ParentToDataDisplayingComponent extends Component {
constructor(props) {
super(props)
this.state = { data: "data loading" }
}
componentWillReceiveProps(nextProps) {
// you can check if incoming props contains the data you need.
if (!this.state.data.length && nextProps.dataLoaded) {
this.setState({ data: mapPropsToDisplayFormat() })
}
}
...
render() {
return (
<div>
<DataDisplayingComponent
data={this.state.data}
/>
</div>
);
}
}
I think depending on what exactly you're checking for in props to see if your data has finished loading, you may be able to use shouldComponentUpdate to achieve a similar result without saving local state.
export default class ParentToDataDisplayingComponent extends Component {
shouldComponentUpdate(nextProps) {
return nextProps.hasData !== this.props.hasData
}
mapPropsToDisplayFormat() {
...
toReturn = data.props.dataThree
? "data loading"
: this.props.dataThree.map(data => ... )
return toReturn;
}
render() {
return (
<div>
<DataDisplayingComponent
data={this.mapPropsToDisplayFormat()}
/>
</div>
);
}
}

Categories