I'm working on a project using react and I have a sign up modal and a login modal that are both separate components and I want to have two links ate the top of each modal to be able to switch from the sign up model to the login model. Each component model has a function open that looks like this:
open() {
this.setState({ showModal: true });
}
Is there a way for a component to call a function and set States from another component or do I need to make both models one component somehow?
The best way to handle communication between components is through a state container for the application that all components "hook in to".
Here's a very simple illustration:
// this state is defined somewhere in your application
// all of your components "hook in to" this state by including
// the values you want as props. For example,
// <MyFancyComponent value={state.value1} />
// and now MyFancyComponent has access to value1
state = {
value1: 42,
showModal1: false,
showModal2: false,
};
// somewhere in your application, there's a render method
// that looks like this
render() {
return (
<div>
{this.props.showModal1 ? <Modal1 /> : null}
{this.props.showModal2 ? <Modal2 /> : null}
{/* now render the rest of your component */}
</div>
);
}
The basic idea is that when this component (the one with the render method above) needs to show Modal1 or Modal2, it changes the appropriate flag in the state, which are mapped to the showModal* props on the component. Then the component re-renders and includes the appropriate modal. If you want to trigger a modal from another component, you change the appropriate flag in your application state & React will go to work re-rendering and show the modal.
The example above is ridiculously incomplete - it's intended to illustrate the basic idea only. To make this work, you'll need to implement a state container for your application. For that, I'd recommend either the flux pattern or redux.
Now, you could implement this as a set of callbacks & properties that are specific to the components you're working with, but I recommend against that - it becomes very difficult to manage, very quickly. Plus, it does not scale - to add a component, you would have to "wire it up" to all your other components manually.
In the component where you are rendering each of these login modals, you would want to pass in values through the props of each component. In the modal components, you would then use the value of the property passed in to determine if the modal should be shown.
Here's a quick example of how it could work (theoretically -- haven't tested):
Login/Signup Modal
import React from 'react';
const LoginModal = React.createClass({
propTypes: {
isVisible: React.PropTypes.boolean.isRequired,
onLogin: React.PropTypes.function,
},
componentWillReceiveProps(nextProps) {
// Will allow parent components to pass in a boolean
// telling this component when to render
this.setState({
showModal: nextProps.isVisible,
});
},
onSubmit() {
// TODO: Handle login
// If we let the parent handle the visibility, we just call
// the onLogin callback passed in and don't set this.state.showModal
this.props.onLogin();
},
render() {
return (
// Use this.state.showModal boolean to show/hide
// your login modal
);
},
});
export default LoginModal;
Parent Component
import React from 'react';
import LoginModal from './LoginModal';
const ParentComponent = React.createClass({
showLoginModal() {
this.setState({
showLogin: true,
});
},
hideLoginModal() {
this.setState({
showLogin: false,
});
// TODO: Likely change the route or do something else here...
},
render() {
return (
<button onClick={this.showLoginModal}>Login</button>
<LoginModal isVisible={this.state.showLogin} onLogin={this.hideLoginModal} />
);
},
});
Related
Is this bad practices or not ?
export state change function from component
import it from other file.
call the function to change state?
In this way we can change some component state from anywhere.
For example...
We want to change the Model.js state from anywhere.
Modal.js
import React from 'react';
export let toggleModal;
export default class Modal extends React.Component {
constructor(props) {
super(props);
this.state = {
open: false,
};
toggleModal = this.toggleModal;
}
toggleModal = () => {
this.setState({ open: !this.state.open });
};
render() {
const { open } = this.state;
return <div style={{ color: 'red' }}>{open && 'Hello Modal'}</div>;
}
}
App.js(Some Top Level component)
import React from 'react';
import Modal from './Modal';
export default () => (
<>
...
<Modal />
...
</>
);
Somewhere.js
import React from 'react';
import {toggleModal} from './Modal';
export default () => (
<>
<h1>Hello!</h1>
<button onClick={() => toggleModal()}>open Modal!</button>
</>
);
But there is no reference in React Official docs, so is this bad practices ?
What React Docs recommends...
Just passing function props to change parent state from parent to children
Use context
Redux or Mobx
But, these are too complex for me.
Example code here
https://next.plnkr.co/edit/37nutSDTWp8GGv2r?preview
Everything seems pretty much overwhelming and difficult at the beginning. But as we get out hands on them, it's give us more confidence to dig into.
I would recommend to use redux that's how we tackled props drilling problem. You can dispatch a action and connect reducer to corresponding component which upon updating state will re render. This is what I recommend to most of the people to learn the tale of redux with a real life example:
Understanding Redux: The World’s Easiest Guide to Beginning Redux
Apart from this you can take Dan Abramov, author of the library, free redux course on egghead.io:
Getting Started with Redux
The problem you run into, almost immediately like your code example does is this:
It will not work: your toggleModal() method expects a this to refer to an actual component instance. When your onClick() handler fires you invoke toggleModal() as a plain function. The this context will be wrong, and so at best (in your example) you will get an error because you try to invoke something undefined, at worst (in general) you end up invoking the wrong method.
When you think about it, for any non-trivial React component you will have a hard time obtaining a reference to the actual instance that is currently being used: you have to make sure that you are not forgetting to invoke the method on the right component instance and also you have to consider that instances may be created/destroyed 'at will' for whatever reason. For example: what if your component is rendered indirectly as part of some other component's render() method? Multiple layers of indirection like that make it even harder.
Now, you could fix all that by abusing ref with abandon but you will find that now you have to keep track of which ref refers to what particular instance, if you happen to have multiple of the components to consider in one render tree...
Whenever you think one component needs to handle the state of its siblings, the solution is usually to lift the state one level up.
export default class Modal extends React.Component {
render() {
const { isOpen } = this.props;
return <div style={{ color: 'red' }}>{isOpen && 'Hello Modal'}</div>;
}
}
export default class Home {
this.state = {
isOpen: false,
};
toggleModal = () => {
this.setState({ isOpen: !this.state.isOpen });
}
render() {
const { isOpen } = this.state;
return (
<>
<h1>Hello {name}!</h1>
<button onClick={() => this.toggleModal()}>open Modal!</button>
<Modal isOpen={isOpen}/>
<p>Start editing and see your changes reflected here immediately!</p>
</>
)
}
}
This way the Home handle the state and your problem is solved.
This can get annoying if the state needs to be "drilled down" to children, that's a problem than redux or react-context can solve.
Here <Modal /> is the child component. So to call a function in a child component you can simply use Ref.
You can refer this page to get more info about Ref.
You can assign a class variable as a ref to this child and use this class variable as an object to call its function.
I found if in special case, my way is okay.
Special case means something like customAlert component.
It is okay only one instance of customAlert component mounted at a time in App.
To achieve this...
1.Use ref to access and change DOM
2.attach state changing function or component to window and call window.function
3.my case: export state changing function and import it from other file.
And here is how to do with react Context
https://next.plnkr.co/edit/EpLm1Bq3ASiWECoE?preview
I think Redux is overkill if the main thing you are interested in is to make some states-like data available and updatable throughout your App without props drilling.
For that purpose, a much simpler approach (maybe not available at the time the question was posted?) is to use react context: https://frontend.turing.edu/lessons/module-3/advanced-react-hooks.html
"context - an API given to us by React, allowing for the passing of
information to child components without the use of props
[...]
useContext - a react hook, allowing functional components to take
advantage of the context API"
I am using the tabnavigator from react navigation. I have a parent component that looks like this
changeService(serviceName){
this.setState({
});
}
render() {
const { container } = styles
return (
<View style={container}>
<Header
clientName = {this.state.project}
serviceName = {this.state.service}
cancelClick = {this.handleExitClick}
/>
<ServicesNavigator
changeService = {this.changeService}
/>
</View>
);
}
Then my service navigator export looks like this
export default (props) => {
const { screenProps, ...otherProps } = props;
const ServicesNavigator = makeServiceNavigator(props);
return <ServicesNavigator screenProps={{ ...screenProps, ...otherProps
}} />
};
The navigator itself looks like a normal navigator except it hsa navigationOptions equal to
tabBarOnPress: (scene, jumpToIndex) => {
changeService(scene.scene.route.routeName)
scene.jumpToIndex(scene.scene.index)
},
Whenever i try to change tabs the navigator is reloading completely and going back to default tab selection. Is there no way to allow the tabnavigors parents state variables to change without reseting the tab navigator?
Whenever tabBarOnPress is triggered, it requests to update state of the parent component. Then due to React component lifecycle, state update will cause parent component to re-render, along with all its children. This is why your tab navigator is reseted all the times.
There are several solutions for your case:
1 - Use shouldComponentUpdate to guide when parent component to be updated. Since you want to retain ServicesNavigator whenever changeService is called, you want to avoid re-rendering on related state items.
shouldComponentUpdate(nextProps, nextState) {
if (this.state.itemA !== nextState.itemA) {
return false; // avoid re-rendering
}
return true;
}
This is dirty but working; however it will haunt you in the long run since it causes parent component's behaviours not idempotent.
Edit: I forgot you have the Header which reads the state. If you use shouldComponentUpdate in the parent component, then Header will not receive new props. So 1st option is not correct. Sorry about this.
2 - Don't let parent component hold the state anymore. In your sample code, parent component has state only because its children components need to communicate and share data with each others. So why don't you just keep the sharing data somewhere else? Best tools can be state management libraries like mobx or redux, which can be integrated quite easily with react-navigation
I have multiple modals for signing up, and having trouble with it. I have three actions in total. First the register button which then activates the first register modal to either sign up with google or facebook, then after that completes the modal for additional information which could not be gathered by the provider will appear with pre-filled inputs gathered form provider. I render the two modals when I render the application and only show it when the register button is clicked. I need the componentDidMount to be called after I complete the facebook or google login, but it was called when I rendered the modals when the app fist started. The buttons hit action reducers and reducers changing the state of type bool on wether to show the modal or not.
class HeaderRegisterButton extends Component {
render() {
return(
<div>
<Register1/>
<Register2/>
</div>
);
}
}
Register Modal 1
class Register1 extends Component {
render() {
return(
<div>
<button onClick={() => this.props.showRegister2()} /> //This would hit the action reducer to get initial info and change the bool to show register 1 to false and register 2 to true.
</div>
);
}
}
Register Modal 2
import { reduxForm, Field, initialize } from 'redux-form';
class Register2 extends Component {
componentDidMount() {
hInitalize() //only called when the app fires, not when the state changes in the action reducer to change bool to show this modal.
}
hInitalize() {
var initData = {};
const initData = {
"name" = this.props.name//name stored inside redux store
};
this.props.initialize(initData)
}
render() {
return(
<div>
//Code to display the modal which works.
</div>
);
}
}
componentDidMount is only called once in the lifecycle of any component, re-render will not reinitialize the component. componentDidUpdate will be called where you can manage your logic.
componentDidMount will execute only once, when React component mounted and it doesn't execut when state or props changed.
therefore you need to use componentDidUpdate and don't forget to compare props or state ( you can also use it when props changed in parent's component )
componentDidUpdate(prevProps,prevState) {
if (this.state.userID !== prevState.userID) {
console.log('userId changed');
}
}
important : DON'T update state in componentDidUpdate without wrappe it in a condition because that triggers re-render and you'll cause an infinite loop
IMO you don't need componentDidMount to be called, simply provide some callbacks to a parent component that holds some temporary state of all the information needed/gathered during the process. Each of your modals will display information given to them by this parent component.
The process would be something like this.
Register Button Clicked
Display choice between Google and Facebook
User Clicks Google or Facebook
Perform Authentication via Social Media Account
Data from authentication sent back to parent component to be stored temporarily
Secondary Registration form displayed with data given to it from parent component
User fills out secondary form
Submit button clicked
Use data from temporary state in parent component for actions
Hope that makes sense :)
UPDATE:
Well I don't know how your implementation is... but you can pass functions into child components (via props) and use them in the child to communicate with the parent, so in the component that does the Facebook authentication have the parent pass someCallback that accepts the parameters shown below and adds them to the parents state (remember a change in state will cause any components who use that state to update). I have done this by passing fbLoginCallback as the callback for my facebook login process which calls Facebook's FB.getLoginStatus(....)
export class FbLoginButton extends Component {
constructor(props) {
super(props);
}
fbLoginCallback = response => {
const { authResponse, status } = response;
if (status === 'connected') {
// Logged into your app and Facebook.
const userId = authResponse.userID;
const accessToken = authResponse.accessToken;
FB.api('/me', user => {
const displayName = user.name;
this.props.someCallback(userId, accessToken, displayName);
});
}
}
}
I've removed the rendering of the of the actual button and few other small things, but that's more or less how the callback should go.
componentDidMount is a lifecycle method that is called only when the component is mounted. It is called only once after the first render.
I'm a bit new to React, so forgive me if this is a bit of a newb question.
I have a base component (Page) which uses state to control whether or not a modal popup is displayed:
constructor(props) {
super(props);
this.state = {
showModal : false,
modalContent : 'Initial Modal Content'
};
this.showModal = this.showModal.bind(this);
this.hideModal = this.hideModal.bind(this);
}
showModal(modalContent) {
this.setState({
showModal : true,
modalContent : modalContent
});
}
hideModal(e) {
this.setState({showModal : false});
}
My problem is that I want a grandchild component to be able to open up my modal.
I know I can do this by passing the state to the child component and then to the grandchild component:
<PartnersTable showModal={this.showModal} partners={PARTNERS} />
That just seems a bit sloppy to me, but maybe that's just the React way.
Can someone let me know if I'm doing this properly or if there's a cleaner way to do this?
You can view my full app on GitHub: https://github.com/CCChapel/Thy-Kingdom-Come/blob/master/react.js
Thanks!
-Eric
You're doing it correctly.
In React the only way for a parent to pass props/state to it's children is by passing it downwards. React is unidirectional from top to bottom; from parent to child only.
So your assumption is correct. It can get pretty sloppy. You must know that React is for presenting the UI and simple cases of state management. When you're application gets more complex and you need to pass down state in a direct and simplified manner use Redux, MobX or any other state containers out there.
If you don't like the complexity of passing down props down the tree considering using Redux (I use Redux myself).
Consider the following resources:
https://egghead.io/courses/getting-started-with-redux
http://redux.js.org/docs/introduction/
https://github.com/reactjs/redux/tree/master/examples
To learn how React works though get used to using only React first then use a state container when the application gets more complex.
In order to achieve that you need to pass your showModal method as a prop to child component(s) in which you want to trigger the visibility of the modal. This is how the render method of the parent component should be:
render() {
return(
<ChildComponent showModal={this.showModal}>
)
}
Then in you child component:
class ChildComponent extends Component {
render() {
return(
<input type="button" onClick={this.props.showModal} value="show modal"/>
)
}
}
In keeping with the one purpose only principle of components, I have a deeply layered component, that many depths below has a open modal button.
When first completing the entire page, I foolishly realized that having the function to open the modal at a very deep level child, caused the modal window to open within that child's parameters and not the window as a whole.
When trying to overcome this, I found myself, too liberally passing down prop functions to sub components that had no need for that prop save to be passed down even further. I can see how this would be a nightmare down the road.
As a last resort I opted for context type:
Main Page Component:
export default class MainPage extends Component {
......
getChildContext() {
return {
openModal: (arg) => this.openModal(arg)
}
}
openModal(content) {
if (content !== undefined) {
const open = content.modalOpen
this.setState({modalOpen: open});
}
}
render() {
.....
return(
{ modalOpen ?
<Modal>
....
</Modal>
: '' }
)
}
}
Many generations down child component:
export default Child extends Component {
....
static contextTypes = {
openModal: PropTypes.func
};
render() {
.....
<img src="/images/shared/zoom_in.png"
onClick={ this.context.openModal.bind(this, { modalOpen: true }) }
....
/>
.....
}
...
}
One reads often where contextType is discouraged and may be done away with in the future, but in this scenario, I cannot see a cleaner, more efficient approach.
Under the scenario I discussed, am I following a prudent approach or might there be a better way?
Thanks!
I would have my child view dispatch an event that can be handled on in the root component.
Webpack and Browserify both have convenient ways to use a nodejs like event emitter in the browser. This is also what most flux implementations use for their dispatcher.
You could export a singleton instance of an event emitter that you require in both child and parent. ie..
import {EventEmitter} from 'events'
exports default new EventEmitter()
then from your child component you can
import emitter from './youemitter'
....
openModal() {
emitter.dispatchEvent(OPEN_MODAL, props)
}
and in your parent you can just
import emitter from './youremitter'
....
componentDidMount() {
emitter.addEventListener(OPEN_MODAL, this.openYourModal)
In the same way you can listen to events from your child views to update your data model or flux store or what you may have and rerender.