Props are undefined on React initialization. Why? - javascript

I am creating a VR application with React 360. What I am trying to do is to eventually create an application as shown here Facebook VR multi surface example. I am taking the code for that example and am trying to fit it into my application. However I'm having a problem with the following initialization of my React components.
I have the following react-360 application with the following code index.js
import React from 'react';
import connect from './Store';
import {
asset,
AppRegistry,
StyleSheet,
Text,
View,
VrButton,
} from 'react-360';
import Entity from 'Entity';
const ModelView = props => {
*********** Problem **************
Why are my props undefined if I am declaring it in the wrapper?
**********************************
return (
<Entity
style={{transform: [{scaleX: 1.25}, {scaleY: 1.25}]}}
source={{obj: asset(`${props.planetName}.obj`), mtl: asset(`${props.planetName}.mtl`)}}
/>
);
};
const ConnectedModelView = connect(ModelView);
export default ConnectedModelView;
AppRegistry.registerComponent('ModelView', () => ModelView);
From the code above, my props for the ModelView should not be undefined. On initialization, it should have the value of earth.
The props are supposed to be initialized in Store.js. Here is the code below:
import React from 'react';
const State = {
planetName: 'earth'
};
const listeners = new Set();
export default function connect(Component) {
return class Wrapper extends React.Component {
state = {
planetName: State.planetName
};
_listener = () => {
this.setState({
planetName: State.planetName
});
};
componentDidMount() {
listeners.add(this._listener);
}
componentWillUnmount() {
listeners.delete(this._listener);
}
render() {
return (
<Component
{...this.props}
planetName={this.state.planetName}
/>
);
}
};
}
Taking a page from the facebook code, what I am doing is initializing model view via Store.connect method. This method creates a wrapper around ModelView where I am setting the props via State.planetName. However, I keep getting undefined and I don't know why. I've hardcoded every part of the code which has State.planetName to the value of earth and it still is undefined. The props are not being set to the value I want and I'm not sure why. Can someone out there assist me with why this might be the case? I would appreciate the help.

It looks like you're rendering the ModelView and not the ConnectedModelView.

Related

How can I connect my mapDispatchToProps to an onClick prop?

I'm having a bit of difficulty implementing redux in a simple react project that I'm creating. For clarification, it's a react 360 webvr project but I've seen many similarities with react native that I'm sure this can work.
The project that I'm trying to do is simply changing the background color of a component on the click of a button. Below is my code:
constants.js
export const PICK_COLOR = 'PICK_COLOR';
actions.js
import { PICK_COLOR } from './constants'
export const pickColor = (color) => ({
type: PICK_COLOR,
payload: color
})
reducers.js
import { PICK_COLOR } from './constants';
const initialColor = {
backgroundColor: 'white'
}
export const chooseColor = (state = initialColor, action={}) => {
switch (action.type) {
case PICK_COLOR:
return Object.assign({}, state, {backgroundColor: action.payload})
default:
return state
}
}
index.js
import React from 'react';
import { createStore } from 'redux';
import { Provider, connect } from 'react-redux';
import { chooseColor } from './reducers';
import { pickColor } from './actions';
import {
AppRegistry,
StyleSheet,
Text,
View,
VrButton
} from 'react-360';
const store = createStore(chooseColor);
const mapStateToProps = state => {
return {
backgroundColor: state.chooseColor.backgroundColor
}
}
const mapDisptachToProps = (dispatch) => {
return {
onChooseColor: (event) => dispatch(pickColor(event.target.value))
}
}
class App extends React.Component {
render() {
const { backgroundColor, onChooseColor } = this.props;
return (
<Provider store={store}>
###########################################
I want this to change background color with
the click of a button.
<View style={[styles.panel, backgroundColor: this.props.backgroundColor]}>
###########################################
<VrButton style={styles.greetingBox} onClick={onChooseColor('blue')}>
<Text style={[styles.greeting, {color: 'blue'}]}>
Blue
</Text>
</VrButton>
</View>
</Provider>
);
}
};
const connectedApp = connect(mapStateToProps, mapDisptachToProps)(App);
AppRegistry.registerComponent('App', () => App);
The problem I'm having is getting over the finish line. I think I have everything set up almost correctly, but I'm unable to trigger any state change. The part where I'm getting confused is how do I connect my onClick prop handler to a state change and pass an argument? I've mixed and matched so many tutorials and videos that my head is spinning at the moment and I'm not entirely wrapping my head about setting up redux yet to troubleshoot effectively.
From what I've gathered, I don't think I have my mapDispatchToProps correctly because in the console I get the error that OnChooseColor is not a function. But how am I supposed to trigger the change? Can someone help pinpoint where I am going wrong? The help would be appreciated.
Could it be that in your mapStateToProps you are reading from state.chooseColor.backgroundColor, but it looks like your store has the shape state.backgroundColor (from what I can tell by the reducers.js)?
It's a bit late for me, so I'll probably have a look at this again tomorrow! (I'll try being more hands-on than just staring at the code!). But I'd definitively try to debug your store in your browser, by setting some breakpoints and having a look at what the store contains. There's also some handy browser extensions for react and redux that I would try out as well! (they should in theory make it easier to see what's going on with redux & react).
I can at least vouch for the react extension myself, I use it heavily just for the feature of being able to tell me which React component I'm looking at (as the DOM renders into <div> and not <MyComponent>!)
Edit: I made a small example that's very similar to this one here!
Two things I can spot by scanning your code.
1. backgroundColor is on the state in reducer.
const mapStateToProps = state => {
return {
backgroundColor: state.backgroundColor
}
}
The function for onClick should be passed instead of calling it.
onClick={() => onChooseColor('blue')}

How to render an instance of a class in reactjs?

I'm trying to render a new instance of an object but I'm not sure how to call the render function or how to set the state from outside the object itself.
//App.js
import React from 'react';
import './App.css';
import DrawMessage from './components/DrawMessage'
function App() {
var Draw = new DrawMessage();
Draw.setState({
test: 'THIS IS A NEW TEST!'
});
return (
<div className="App">
<header className="App-header">
<DrawMessage/>
<Draw/>
</header>
</div>
);
}
export default App;
//DrawMessage.js
import React from 'react'
class DrawMessage extends React.Component {
constructor (props){
super(props)
this.state = {
test: 'THIS IS A TEST!'
}
}
render () {
return <div className='message-box'>
Hello {this.state.test}
</div>
}
}
export default DrawMessage
using the DrawMessage in the return yields "Hello THIS IS A TEST!" which is the default response
But if I was able to render the Draw i was expecting to see "Hello THIS IS A NEW TEST!" as i want to change the state of "test"
I am assuming that I cant call the render function from Draw the way i did and I don't think I am properly changing the state of test.
Calling setState on a child from a parent is not conventional React. I'd remove all of the following, and instead just invoke DrawMessage in the return of App:
var Draw = new DrawMessage();
Draw.setState({
test: 'THIS IS A NEW TEST!'
});
One way to achieve your goal would be to set the state of DrawMessage in its constructor function, and then change it via componentDidUpdate() when the props passed from App change. If you're considering that route, ask yourself the following: does DrawMessage need to have its own state? If its state is often changed from the parent, it may be better served as part of props. React will re-render DrawMessage when props change.
componentDidUpdate() docs
Via JSX: var myTinyObject = <TinyObject prop1={prop1} prop2={prop2} />;
Via React.createElement: var myTinyObject = React.createElement(TinyObject, { prop1, prop2 }, null);

Display Component based on another component lifecycle

I have recently encountered an issue regarding the usage of one of my costum components. I have created a "Chargement" (Loading in French) Component for a project I am working on.
This component is a simple circular spinner with a dark background that when displayed, informs the user that an action is going on.
import React, {Fragment} from 'react';
import { CircularProgress } from 'material-ui/Progress';
import blue from 'material-ui/colors/blue';
import PropTypes from 'prop-types';
import { withStyles } from 'material-ui/styles';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
const styles = theme => ({
chargement: {
position: 'fixed',
left: '50%',
top: '50%',
zIndex: 1
}
});
class Chargement extends React.Component {
render () {
const { classes } = this.props;
if (this.props.chargement) {
return (
<Fragment>
<div className='loadingicon'>
<CircularProgress size={80} style={{ color: blue[500] }}/>
</div>
<div className='loadingBackground'/>
</Fragment>
);
} else {
return null;
}
}
}
const mapStateToProps = (state) => {
return {
chargement: state.App.chargement
};
};
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({
}, dispatch);
};
Chargement.propTypes = {
classes: PropTypes.object.isRequired
};
let ChargementWrapped = withStyles(styles)(Chargement);
export default connect(mapStateToProps, mapDispatchToProps)(ChargementWrapped);
This component is displayed based on a boolean variable in my redux Store called "chargement".
It works like a charm whenever I am using it to make api call and load data. However, one of the components in my Web App takes quite a bit of time to render (1-2 seconds). This component renders a pretty big list of data with expansion panels. I tried to set my display variable based on the componentWillMount and componentDidMount functions.
class ListView extends React.Component {
componentWillMount () {
this.props.setChargement(true);
}
componentDidMount () {
this.props.setChargement(false);
}
However with this particular case the "chargement" component never displays.
I also tried to create a "Wrapper Component" in case the issue came from my "chargement" component being somewhat related to the re-rendered component as a children. :
export default class AppWrapper extends React.Component {
render () {
return (
<Fragment>
<Reboot />
<EnTete />
<Chargement />
<App />
</Fragment>
);
}
}
The "App " component is the one that takes a few seconds to render and that I am trying to implement my "chargement" component for. I am pretty sure this as to do with the component lifecycle but everything I tried so far failed.
My current stack is : React with Redux and MaterialUi
What am I missing ?
Thanks for your help!
Ps: You might want to check the explanation and precision I added on the main answer comments as they provide further context.
Not sure if I understood correctly, but I think the problem is simply your API call takes more time than your component mounting cycle, which is totally normal. You can solve the problem by rearranging a bit the places where to put the IO.
Assuming you are making the API call from AppWrapper, dispatch the Redux action in componentDidMount i.e. fetchListItems(). When the API call resolves, the reducer should change its internal loading value from true to false. Then, AppWrapper will receive chargement as a prop and its value will be false. Therefore, you should check what this value is in AppWrapper's render method. If the prop is true, you render the Chargement component or else, render ListView.
Also, try always to decouple the IO from the view. It's quite likely that you'll need to reuse Chargement in other situations, right? Then, make it a simple, generic component by just rendering the view. Otherwise, if you need to reuse the component, it will be coupled to one endpoint already. For this, you can use a Stateless Functional Component as follows:
const Chargement = () =>
<Fragment>
<div className='loadingicon'>
<CircularProgress size={80} style={{ color: blue[500] }}/>
</div>
<div className='loadingBackground'/>
</Fragment>
I found a way to fix my issue that does not involve the use of the "chargement" component like I had initially planned. The issue revolved around the usage of Expansion Panels from the Material-Ui-Next librairy.
The solution I found is the following :
Instead of trying to show a Loading component while my list rendered, I reduced the render time of the list by not rendering the ExpansionDetail Component unless the user clicked to expand it.
This way, the list renders well under 0.2 seconds on any devices I've tested. I set the state to collapsed: false on every panel inside the constructor.
class ListItem extends React.Component {
constructor (props) {
super(props);
this.state = {
collapsed: false
};
this.managePanelState = this.managePanelState.bind(this);
}
managePanelState () {
if (this.state.collapsed) {
this.setState({collapsed: false});
} else {
this.setState({collapsed: true});
}
}
Then I use the onChange event of the expansion panel to switch the state between collapsed and not on every ListItemDetail element.
<ExpansionPanel onChange={() => this.managePanelState()}>
I guess sometimes the solution isn't where you had initially planned.
Thanks to everyone who took time to look into my problem!

Why can't I curry a react component?

I've been getting started with react-redux and finding it a very interesting way to simplify the front end code for an application using many objects that it acquires from a back end service where the objects need to be updated on the front end in approximately real time.
Using a container class largely automates the watching (which updates the objects in the store when they change). Here's an example:
const MethodListContainer = React.createClass({
render(){
return <MethodList {...this.props} />},
componentDidMount(){
this.fetchAndWatch('/list/method')},
componentWillUnmount(){
if (isFunction(this._unwatch)) this._unwatch()},
fetchAndWatch(oId){
this.props.fetchObject(oId).then((obj) => {
this._unwatch = this.props.watchObject(oId);
return obj})}});
In trying to supply the rest of the application with as simple and clear separation as possible, I tried to supply an alternative 'connect' which would automatically supply an appropriate container thus:
const connect = (mapStateToProps, watchObjectId) => (component) => {
const ContainerComponent = React.createClass({
render(){
return <component {...this.props} />
},
componentDidMount(){
this.fetchAndWatch()},
componentWillUnmount(){
if (isFunction(this._unwatch)) this._unwatch()},
fetchAndWatch(){
this.props.fetchObject(watchObjectId).then((obj) => {
this._unwatch = this.props.watchObject(watchObjectId);
return obj})}
});
return reduxConnect(mapStateToProps, actions)(ContainerComponent)
};
This is then used thus:
module.exports = connect(mapStateToProps, '/list/method')(MethodList)
However, component does not get rendered. The container is rendered except that the component does not get instantiated or rendered. The component renders (and updates) as expected if I don't pass it as a parameter and reference it directly instead.
No errors or warnings are generated.
What am I doing wrong?
This is my workaround rather than an explanation for the error:
In connect_obj.js:
"use strict";
import React from 'react';
import {connect} from 'react-redux';
import {actions} from 'redux/main';
import {gets} from 'redux/main';
import {isFunction, omit} from 'lodash';
/*
A connected wrapper that expects an oId property for an object it can get in the store.
It fetches the object and places it on the 'obj' property for its children (this prop will start as null
because the fetch is async). It also ensures that the object is watched while the children are mounted.
*/
const mapStateToProps = (state, ownProps) => ({obj: gets.getObject(state, ownProps.oId)});
function connectObj(Wrapped){
const HOC = React.createClass({
render(){
return <Wrapped {...this.props} />
},
componentDidMount(){
this.fetchAndWatch()},
componentWillUnmount(){
if (isFunction(this._unwatch)) this._unwatch()},
fetchAndWatch(){
const {fetchObject, watchObject, oId} = this.props;
fetchObject(oId).then((obj) => {
this._unwatch = watchObject(oId);
return obj})}});
return connect(mapStateToProps, actions)(HOC)}
export default connectObj;
Then I can use it anywhere thus:
"use strict";
import React from 'react';
import connectObj from 'redux/connect_obj';
const Method = connectObj(React.createClass({
render(){
const {obj, oId} = this.props;
return (obj) ? <p>{obj.id}: {obj.name}/{obj.function}</p> : <p>Fetching {oId}</p>}}));
So connectObj achieves my goal of creating a project wide replacement for setting up the connect explicitly along with a container component to watch/unwatch the objects. This saves quite a lot of boiler plate and gives us a single place to maintain the setup and connection of the store to the components whose job is just to present the objects that may change over time (through updates from the service).
I still don't understand why my first attempt does not work and this workaround does not support injecting other state props (as all the actions are available there is no need to worry about the dispatches).
Try using a different variable name for the component parameter.
const connect = (mapStateToProps, watchObjectId) => (MyComponent) => {
const ContainerComponent = React.createClass({
render() {
return <MyComponent {...this.props} obj={this.state.obj} />
}
...
fetchAndWatch() {
fetchObject(watchObjectId).then(obj => {
this._unwatch = watchObject(watchObjectId);
this.setState({obj});
})
}
});
...
}
I think the problem might be because the component is in lower case (<component {...this.props} />). JSX treats lowercase elements as DOM element and capitalized as React element.
Edit:
If you need to access the obj data, you'll have to pass it as props to the component. Updated the code snippet

If I need to get properties for a component from an API should I do that before the component loads?

Say I have a comp that is inside of a Scene (react-native-router-flux). It lets people choose their favorite fruits.
import React, {Component} from 'react';
import {View, Text, StyleSheet} from 'react-native';
import {MKCheckbox} from 'react-native-material-kit';
var styles = StyleSheet.create({});
export default class PickAFruit extends Component {
render() {
console.log(this.props.fruits);
return (
<View>
{
this.props.fruits.map((x)=> {
return (
<View key={x.key}>
<Text>{x.key}</Text>
<MKCheckbox checked={this.props.checked} key={x.key} onCheckedChange={(e) => {
this.props.update(e, '' + x.key)
}}/>
</View>
)
})
}
</View>
)
}
}
In the parent comp I'm loading the list of fruits from an API in the didMount:
componentDidMount() {
ApiInst.getFruits().then((fruits) => {
console.log(fruits);
console.log(this.props.fruits);
this.props.fruits = fruits;
});
}
I'm also setting a default fruits array in the parent class. It seems like the properties won't load via the API though, the list of fruit is always the "unknown" value, never the new values. Do I need to load the list of fruits before the Profile scene is loaded? When is the correct time to set properties for a component if they will come from an API?
setState seems like the easy answer but these settings don't "feel" like state, they feel like properties that would be injected at build-time (i.e. when the component is built, not the app). Is this a distinction without a real difference?
You can't modify props. Props are passed from parent to child component, and only the parent can change them.
Use setState instead:
this.setState({fruits: fruits});
And access them from state:
<PickAFruit fruits={this.state.fruits} />
You may also want to set a default state in the component constructor:
constructor(props) {
super(this);
this.state = {fruits: null};
}
this.props.fruits = fruits;
won't effect child component, and to be honest - I'm not sure it will work at all. If you don't want to use flux architecture I think the best solution is to update parent's state on componentDidMount() and pass it as props to child component:
componentDidMount() {
ApiInst.getFruits().then((fruits) => {
this.setState({fruits: fruits});
});
}
render() {
return (
<PickAFruit fruits={this.state.fruits} />
);
}
Every state change will invokre render() method, so after API call PickAFruit component will be rerendered, with fruits passed as a props.

Categories