How can I get Component from React Relay Container to get static variable ?
import Home from './pages/home';
console.log(Home.route) // undefined
I need something like Home.Component.route // object
class Home extends React.Component {
static route = {
component: Home,
route: HomeRoute,
leftButton: false,
sideMenu: false,
};
render() {
return (
<View style={styles.container}>
<Text>
Home
</Text>
<Text>
{this.props.greetings.hello}
</Text>
</View>
);
}
}
export default Relay.createContainer(Home, {
fragments: {
greetings: () => Relay.QL`
fragment on Greetings {
hello,
}
`,
}
});
I don't think there's a way to get at this statically without doing one of the following.
Alternative 1: Add an explicit export of the wrapped component
// In "Home.js":
export class HomeComponent extends React.Component {
static route = {
// ...
}
}
export default Relay.createContainer(HomeComponent, {
// ...
});
// In "Other.js":
import Home, {HomeComponent} from './Home.js';
console.log("should have access here", HomeComponent.route);
Alternative 2: Add properties to the container before exporting
const HomeContainer = Relay.createContainer(
// ...
);
HomeContainer.route = {
// ...
};
export default HomeContainer;
Alternative 3: Access via refs at runtime
Once the container is mounted and you have a reference to it you can get at the original component via the "component" ref. Note that this is not documented and therefore not guaranteed to work in the future.
// In "Other.js":
render() {
return (
<Home
ref={
container => (
console.log(container.refs.component.route)
)
}
/>
);
}
Obviously, this is the hackiest of the three alternatives, so I probably wouldn't recommend it.
Related
Is there a way of delegating in react native just like in swift/ios.. If we want to pass a information to a child class from parent class, we can do it by passing props. But what if we want to pass some information from the child class to parent class.
PS: No Redux or singleton approach.
Callbacks are common in React and could be used for such a thing.
// Child.js
export default class Child extends Component {
componentDidMount() {
this.props.onEvent({ type : "event" });
}
render() {
return (
<Text>I'm a child</Text>
);
}
}
// Parent.js
export default class Parent extends Component {
const onChildEvent = (event) => {
console.log(event); // { type : "event" }
}
render() {
return (
<Child onEvent={this.onChildEvent} />
);
}
}
In my React Native application I'm trying to change state and trigger a re-render of a component. It should be done when NavBottom calls this.props.navigation.navigate('captureView') to navigate to CaptureView. The state update should reset the CaptureView photo state variable back to its original value.
How can state be changed in React Native with react-navigation on navigate? https://reactnavigation.org/docs/en/navigation-actions.html
CaptureView is part of CaptureStack
import { createStackNavigator } from "react-navigation";
const CaptureStack = createStackNavigator({
captureView: CaptureView,
detailView: DetailView,
reportView: ReportView,
});
const Tab = createBottomTabNavigator({
capture: CaptureStack,
}, {
initialRouteName: 'capture',
tabBarComponent: NavBottom
});
CaptureView.js:
import { StackActions, NavigationActions, NavigationEvents } from 'react-navigation';
class CaptureView extends Component {
static navigationOptions = {}
constructor(props) {
super(props);
console.log("CaptureView: constructor(props)");
}
componentDidMount() { // called just once
console.log("CaptureView: componentDidMount()");
// this.setState = { // undefined???
// photo: null // this needs to be RESET
// };
}
componentWillUnmount() {
console.log("CaptureView: componentWillUnmount()");
}
async onButtonPress() {
CameraService.takePicture().then((photo)=>{
this.setState({
photo: photo
});
// More actions
this.props.navigation.navigate(
"detailView",
{
id: 'DetailView',
photo
}
);
});
}
render() {
return (
<Camera
cameraSetter={(cam) => {
CameraService.setCamera(cam)
}}
photo={this.state.photo}
/>
<TouchableOpacity onPress={this.onButtonPress.bind(this)}>
<Text>TAKE PHOTO</Text>
</TouchableOpacity>
);
}
}
Then in other part of the application there is a button to navigate back to CaptureView.
NavBottom.js:
export default class NavBottom extends Component {
constructor(props) {
super(props);
}
render() {
return (
<View>
<TouchableOpacity onPress={() => this.props.navigation.navigate('captureView')}>
<Text>CAMERA</Text>
</TouchableOpacity>
</View >);
}
}
Notes
I've tried different ways from ReactJS (not React Native) documentation that failed:
https://reactjs.org/docs/react-component.html#componentdidmount - componentDidMount() seems to bee the recommended way in documentation, but in my application it gets called ONLY ONCE?
Even when componentDidMount() is called once at the beginning, but even then this.setState() is undefined. It's strange, the documentation says it should be available
have you tried using navigation events to reset the state of your CaptureView component?
I think onWillFocus might do the trick.
https://reactnavigation.org/docs/en/navigation-events.html
React Navigation doesn't re-render a component in a stack that resides in Tab Naviagtor
in order perform forceful re-render than you can use event listener like this
componentDidMount() {
this._navListener = this.props.navigation.addListener('didFocus', () => {
//perform any action you want, place your logic here
});
}
you can also use React Navigation's HOC withNavigation this pass a ( isFocused ) prop to the connected component
componentDidUpdate(prevProps, prevState) {
if(prevProps.isFocused !== this.props.isFocused){
//place your desired logic here
}
}
Usage of withNavigation
import { withNavigationFocus } from 'react-navigation';
...
...
...
export default withNavigationFocus(YourComponent)
I'm building an moving web application with the following relationships: a USER has many MOVES --> a MOVE has many BOXES --> a BOX has many ITEMS.
I have the functionality working so that when you click on a specific MOVE, you are routed to a list of all the BOXES associated with that MOVE.
However, if I am on the list of BOXES page and click back to the list of MOVES page, my MOVES duplicate. And similarly, when I click back into a specific MOVE, my BOXES duplicate. And at some points, if I do a total page refresh, my data either 1. Completely disappears (i.e., componentDidMount() is perhaps not working on page refresh?) OR 2. my app breaks and receive this error: "Invalid attempt to spread non-iterable instance".
My goal is just to have the back and forward functionality working properly so that my data is mounted once and just stays the same.
I have tried fixing the issue within my componentDidMount() method inside of both my MoveList and BoxList component. I looked into some posts about browser history and tried the following in order to limit my componentDidMount() to ONE TIME.
componentDidMount() {
if (this.props.history.action === "POP") {
this.props.getMoves(this.props.user.user_id)
}
}
MoveList.js
import React from 'react';
import { connect } from 'react-redux';
import { getMoves } from '../actions/moveActions'
import Move from './Move'
import { withRouter } from 'react-router-dom'
class MoveList extends React.Component {
componentDidMount() {
if (this.props.history.action === "POP") {
this.props.getMoves(this.props.user.user_id)
}
}
render() {
const mappedMoves = this.props.moves.map((move) => {
return <Move move={move} key={move.id} />
})
return (
<div className="row">
{mappedMoves}
</div>
)
}
}
const mapStateToProps = (state) => {
return {
moves: state.moves,
user: state.user
}
}
const mapDispatchToProps = (dispatch) => {
return {
getMoves: () => dispatch(getMoves())
}
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(MoveList));
BoxList.js (u click a Move to see this box list)
import React from 'react';
import Box from './Box';
import { withRouter } from 'react-router-dom'
import { connect } from 'react-redux';
import { getBoxes } from '../actions/boxActions'
class BoxList extends React.Component {
componentDidMount() {
if (this.props.history.action === "PUSH") {
this.props.getBoxes(this.props.user.user_id, this.props.move.id)
}
}
render() {
console.log("boxlist props", this.props.history);
const mappedBoxes = this.props.boxes.map((box, idx) => {
return <Box box={box} key={box.id} idx={idx}/>
})
return (
<div className="col s9">
{mappedBoxes}
</div>
)
}
}
const mapStateToProps = state => {
return {
move: state.move,
boxes: state.boxes,
user: state.user
}
}
const mapDispatchToProps = dispatch => {
return {
getBoxes: (userId, moveId) => dispatch(getBoxes(userId, moveId))
}
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(BoxList))
The expected result happens on the first load. However, if I click back and forth the data either dupes or disappears.
Here's my router:
class App extends Component {
render() {
return (
<Fragment>
<div className="App">
<NavBar />
<Switch>
<Route exact path="/moves" component={MoveContainer} />
<Route path="/boxes" render={(props) => <BoxContainer {...props} />} />
</Switch>
</div>
</Fragment>
);
}
}
This problem occurs, because every time you go back to the page, the component is reassembled, and the getMoves action is triggered, thus adding the data to the array, even the ones already in it. To resolve this, you can add elements to your array as follows:
function uniqueData(value, index, self) {
return self.indexOf(value) === index;
}
moves = moves.concat(data).filter(uniqueData);
As we can see above, the array moves is concatenated with the array data, which in this case would refer to the data that will be added in the array moves, and soon after being concatenated, it is filtered, using the uniqueData function, thus removing all duplicate data from the array moves.
So I started converting my application from ES2015 to ES6 which uses React.
I have a parent class and a child class like so,
export default class Parent extends Component {
constructor(props) {
super(props);
this.state = {
code: ''
};
}
setCodeChange(newCode) {
this.setState({code: newCode});
}
login() {
if (this.state.code == "") {
// Some functionality
}
}
render() {
return (
<div>
<Child onCodeChange={this.setCodeChange} onLogin={this.login} />
</div>
);
}
}
Child class,
export default class Child extends Component {
constructor(props) {
super(props);
}
handleCodeChange(e) {
this.props.onCodeChange(e.target.value);
}
login() {
this.props.onLogin();
}
render() {
return (
<div>
<input name="code" onChange={this.handleCodeChange.bind(this)}/>
</div>
<button id="login" onClick={this.login.bind(this)}>
);
}
}
Child.propTypes = {
onCodeChange: React.PropTypes.func,
onLogin: React.PropTypes.func
};
However this causes the following error,
this.state is undefined
It refers to,
if (this.state.code == "") {
// Some functionality
}
Any idea what could be causing this ?
You can use arrow function to bind you functions. You need to bind you functions both in child as well as parent components.
Parent:
export default class Parent extends Component {
constructor(props) {
super(props);
this.state = {
code: ''
};
}
setCodeChange = (newCode) => {
this.setState({code: newCode});
}
login = () => {
if (this.state.code == "") {
// Some functionality
}
}
render() {
return (
<div>
<Child onCodeChange={this.setCodeChange} onLogin={this.login} />
</div>
);
}
}
Child
export default class Child extends Component {
constructor(props) {
super(props);
}
handleCodeChange = (e) => {
this.props.onCodeChange(e.target.value);
}
login = () => {
this.props.onLogin();
}
render() {
return (
<div>
<input name="code" onChange={this.handleCodeChange}/>
</div>
<button id="login" onClick={this.login}>
);
}
}
Child.propTypes = {
onCodeChange: React.PropTypes.func,
onLogin: React.PropTypes.func
};
There are other ways to bind the functions as well such as the one you are using but you need to do that for parent component too as <Child onCodeChange={this.setCodeChange.bind(this)} onLogin={this.login.bind(this)} />
or you can specify binding in the constructor as
Parent:
constructor(props) {
super(props);
this.state = {
code: ''
};
this.setCodeChange = this.setCodeChange.bind(this);
this.login = this.login.bind(this);
}
Child
constructor(props) {
super(props);
this.handleCodeChange = this.handleCodeChange.bind(this);
this.login = this.login.bind(this);
}
I agree with all different solutions given by #Shubham Kathri except direct binding in render.
You are not recommended to bind your functions directly in render. You are recommended to bind it in constructor always because if you do binding directly in render then whenever your component renders Webpack will create a new function/object in bundled file thus the Webpack bundle file size grows. For many reasons your component re-renders eg: doing setState but if you place it in constructor it gets called called only once.
The below implementation is not recommended
<Child onCodeChange={this.setCodeChange.bind(this)} onLogin={this.login.bind(this)} />
Do it in constructor always and use the ref wherever required
constructor(props){
super(props);
this.login = this.login.bind(this);
this.setCodeChange = this.setCodeChange.bind(this);
}
<Child onCodeChange={this.setCodeChange} onLogin={this.login} />
If you are using ES6 then manual binding is not required but if you want you can. You can use arrow functions if you want to stay away with scope related issues and manual function/object bindings.
Sorry if there are any typos I am answering in my mobile
I have implemented this but the store has no values (all undefined):
This is the store:
export default class AppState {
// Is authenticated
#observable authenticated;
#action get authenticated() {
return this.authenticated;
}
doSomethingWithNoDecorator() {
return this.authenticated;
}
}
This is index.js:
const stores = {
AppState
};
const renderApp = Component => {
render(
<AppContainer>
<Provider { ...stores }>
<Router>
// Routes
</Router>
</Provider>
</AppContainer>,
document.getElementById("root")
);
};
This is the Component:
#inject("AppState")
#observer
export default class SidebarListItem extends Component {
constructor(props) {
super(props)
this.store = this.props.AppState;
}
doSomething() {
this.store.authenticated();
this.store.doSomethingWithNoDecorator();
this.store.authenticated;
}
}
The store is not null... I can see the function. But I can't get any field or invoke any method.
What did I do wrong?
Regards,
Idob
You need to initialise your store:
const stores = { AppState: new AppState() }
By the way, #actions cannot be applied to getters.