How to call method of React component outside of a class? - javascript

I have a modal in my React one page app. I made a class component of modal and render it in some place on the page. I need to call it from many other pages in app this modal.
My modal class:
class Modal extends React.Component {
constructor(props) {
super(props);
this.state = {
modalShow: false,
}
}
setModalShow(state){
this.setState({modalShow: state});
}
render(){...}
}
State of modal I keep in modal class. I want to show modal by changing state from outside Modal class. For examle something like
import Modal from './Modal'
...
Modal.setModalShow(true)
But Modal its a class, but I need to call setModalShow in instance of a class. And I don't understand how to do such a thing in React true way.

That kind of behavior requires passing down that function as a child property, Like this:
class Modal extends React.Component {
constructor(props) {
super(props);
}
setModalShow(state){
this.props.showModal(true);
}
render(){
...
}
}
And wherever you use the modal, there should be the showing state like:
class ModalWrapper extends React.Component {
constructor(props) {
super(props);
this.state = {
showModal: false
}
}
showModal(state){
this.setState({modalShow: state});
}
render(){
return (<Modal showModal={showModal} />);
}
}

While you can, as other suggests, pass a function that will allow you to register the method to another component, this is probably not the "React" way to open a modal outside of the component (meaning that there are ways that will, I believe, be clearer to write in React than this). I would suggest either making the open state a prop or maintain the modal state in a react context (or even pass the opening function as a context, if more things happen in it than simply opening the modal), which will allow you to avoid prop drilling.
You can read about the context API in the React docs: https://reactjs.org/docs/context.html. Note that it is far simpler to use as a hook in a functional component, but it works fine with classes as well.

you should pass the method setModalShow from Modal to one of its children, and then the child component would call this.props.setModalShow(true).

I desided to use Redux. In Motivation is described my problem.

Related

What triggered first in react native?

I have a react native component which contains both WillFocus and componentDidMount functions.
My Questions is, if I navigate to this component which function is getting triggered first? 'WillFocus' or 'componentDidMount'
Sample code is showing below
class Notifications extends Component {
static navigationOptions = {
header: null
}
constructor(props) {
super(props);
const{navigation}=this.props
this.state = {
highlightHome : true,
highlightNotifications: true,
}
}
willFocus = [this.props.navigation.addListener(
'willFocus',
payload => {
console.log('willFocus')
}
)]
componentDidMount() {
console.log('componentDidMount')
}
}
React Navigation emits events to screen components that subscribe to them.
componentDidMount:
This method is called once all our children Elements and our Component instances are mounted onto the Native UI. When this method is called we now have access to the Native UI (DOM, UIView, etc.), access to our children refs and the ability to potentially trigger a new render pass.
willFocus:
the screen will focus.
By definition, willFocus will be called after ComponentDidMount because it mounted all the UI components.
componentDidMount call first, but in the next time (back or something...) just willFocus call

Change the content (different languages) of React Components when clicking on a button

I want to create a React Website where you can click on a button and change the language to German or English. I'm new to React and still learning.
I created a simple JSON that looks like this:
{
"de": {
"header": "Willkommen",
"footer": "Auf Wiedersehen"
},
"eng": {
"header": "Welcome",
"footer": "Goodbye"
}
}
I created a button that toggles its state when onClick. But I think state is the wrong way to do this if I want to access it everywhere.
class Button extends React.Component {
constructor(props) {
super(props);
this.state = {isOn: true};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
if (this.state.isOn) {
this.setState({isOn: false})
} else {
this.setState({isOn: true})
}
}
render() {
return(
<button type="button" onClick={this.handleClick}>DE/ENG{JSON.stringify(this.state.isOn)}</button>
);
}
}
And now I want to import the JSON data and put the right one in the components. This one down here isn't working. I don't know how to do it. Every component should get the German or English data when the button is clicked. I want to use it in many components. (It's working when I use const data = DATA.de for example but that won't change the content when I click the button.)
import DATA from './data.json';
class Main extends React.Component {
render() {
const data = Button.state.isOn ? DATA.de : DATA.eng;
return (
<div>
<Button onIsOnChange={this.buttonChange} />
<span>{data.header}</span>
</div>
);
}
}
Do you know how to do this?
React encourages you to "lift state", and this is a great opportunity to do so. I'd recommend you remove the state and handleClick method from the Button element (try using a functional stateless component), and instead add the same state (on/off) to the Main element. When the button is clicked, simply make a call (using the onClick attribute) to this.props.handleClick. You'll need to give handleClick as a prop to the button, and in your Main element's handleClick method, set your state accordingly. You'll also need to give your Button component an isOn prop.
In Main your data const will then respond to this.state.isOn.
I would suggest to change your design a bit. Create a language change (call it 'onLanguaheChange' for example) method on Main component and pass it as a prop to you Button component,
<Button onClick={this.onLanguageChange}.../>
now, inside Button component, you can just call this.props.onClick(...) and pass it the selected/required language. It will call the Main componemt method onLanguageChange with your parameter. Then you can change Main's state as desired, and pass it to what ever component you'd like.

In reactjs how can I call a method from another component?

Just trying reactjs and came across the situation that I want to call a method on another component:
class MyComp extends React.Component {
callMe(){
...
}
}
So mycomp2:
import MyComp from 'myComp';
class MyComp2 extends React.Component {
test(){
MyComp.callMe();
}
}
How can I do this ?
If the method callMe doesn't use this, you can declear it as static to use it like that.
class MyComp extends React.Component {
static callMe(){
...
}
}
If not, you can use ref to make it work.
You may need:
https://facebook.github.io/react/docs/refs-and-the-dom.html
Cannot. You cannot do that in react component. The only way is to move that function to their common ancestor.
Of course, there is another way, why not consider redux? let's react component talk through redux state. that way, you will never have this puzzle.
It depends on how your components are related to one another. If one is a parent and the other is a child then the function can be passed as a prop from parent to child, e.g.
class Parent extends React.Component {
callMe() {
console.log('called');
}
render() {
return (
<Child doSomething={() => this.callMe()} />
);
}
}
class Child extends React.Component {
render() {
<button onClick={this.props.doSomething}>Calls the parent fn</button>
}
}
Alternatively if they are not a parent-child relationship, then you need to introduce a parent component in which the function can live, then pass it via props in the same way.
If neither of these approaches seems to fit, then let us know what behaviour you are trying to achieve that requires this, and there my well be a different way to structure your code.

How to access child component functions via refs

The docs for React state that component functions can be accessed by a parent component via refs. See: https://facebook.github.io/react/tips/expose-component-functions.html
I am attempting to use this in my application but run into an "undefined is not a function" error when the child function is called. I'm wondering if this has anything to do with using the ES6 format for React classes because I don't see any other differences between my code and the docs.
I have a Dialog component that looks like the following pseudocode. The Dialog has a "Save" button that calls save(), which needs to call the save() function in the child Content component. The Content component collects information from child form fields and performs the save.
class MyDialog extends React.Component {
save() {
this.refs.content.save(); <-- save() is undefined
}
render() {
return (
<Dialog action={this.save.bind(this)}>
<Content ref="content"/>
</Dialog>);
}
}
class Content extends React.Component {
save() {
// Get values from child fields
// and save the content
}
}
I could instead pass a prop (saveOnNextUpdate) down to Content and then execute save whenever it is true, but I would rather figure out how to get the method detailed in the React doc above to work.
Any ideas on how to get the doc approach to work or access the child component function in a different way?
Redux connect accepts an option parametre as the forth parameter. In this option parameter you can set the flag withRef to true. Then you can access functions to refs by using getWrappedInstance(). Like this:
class MyDialog extends React.Component {
save() {
this.refs.content.getWrappedInstance().save();
}
render() {
return (
<Dialog action={this.save.bind(this)}>
<Content ref="content"/>
</Dialog>);
}
}
class Content extends React.Component {
save() { ... }
}
function mapStateToProps(state) { ... }
module.exports = connect(mapStateToProps, null, null, { withRef: true })(Content);
Read more about it here: https://github.com/reactjs/react-redux/blob/master/docs/api.md#connectmapstatetoprops-mapdispatchtoprops-mergeprops-options
Worth reading this article about use of refs and consider if there's better approaches: https://facebook.github.io/react/docs/refs-and-the-dom.html#dont-overuse-refs
An alternative way to do this would be to use some other prop name (other than ref). I've found that this also works well if you're using a library like styled-components or emotion For example in a connected MyComponent:
<MyComponent
...
innerRef={(node) => { this.myRef = node; }}
/>
As it turns out, m90 was right -- this was a different issue entirely. I'm posting the solution in case someone runs into the same problem in the future.
My application is built with Redux, and the problem stems from using the react-redux connect function to connect a component to the store/global state. For some reason, exporting a component and connecting it to the store makes it impossible to access the functions inside of it. In order to get around this, I had to remove all use of global state from Content so that I could export it as a "dumb" component.
To be more clear, Content.js looked like this:
var connect = require('react-redux').connect;
class Content extends React.Component {
save() {
// Get values from child fields
// and save the content
// Use of this.props.stateObject
}
}
function mapStateToProps(state) {
const {
stateObject
} = state;
return {
stateObject
};
}
module.exports = connect(mapStateToProps)(Content);
Removing the use of global state (and therefore the use of connect and mapStateToProps allowed me to export the component using:
module.exports = Content;
Accessing this.refs.content.save() magically worked after doing this.

React + Alt: Lifecycle Gap

Edit: Check out the git repository for a minmal example: https://github.com/maximilianschmitt/blind-lifecycle
I have a component RequireUser that tries to ensure that the user is logged in and will otherwise not render its children. Its parent component, App, should know if a user is required and render a login form if needed.
The problem is, that the App component mounts AFTER the RequireUser component in a tree like this:
App
RequireUser
SomeOtherComponent
In RequireUser's componentDidMount I am triggering an action requireLogin that sets the UserStore's loginRequired variable to true.
This does not update the parent component (App) because it has not yet been mounted and can therefor not register changes to the store.
class RequireUser extends React.Component {
constructor() {
super();
this.state = alt.stores.UserStore.getState();
}
componentDidMount() {
this.unlisten = alt.stores.UserStore.listen(this.setState.bind(this));
if (!this.state.requireUser) {
UserActions.requireUser();
// using setTimeout will work:
// setTimeout(() => UserActions.requireUser());
}
}
componentWillUnmount() {
this.unlisten();
}
render() {
if (this.state.requireUser) {
return <div>I have required your user</div>;
}
return <div>I will require your user</div>;
}
}
class App extends React.Component {
constructor() {
super();
this.state = alt.stores.UserStore.getState();
}
componentDidMount() {
this.unlisten = alt.stores.UserStore.listen(this.setState.bind(this));
}
componentWillUnmount() {
this.unlisten();
}
render() {
return (
<div>
<div>User required? {this.state.requireUser + ''}</div>
<RequireUser />
</div>
);
}
}
Output:
User required? false
I have required your user
If I use setTimeout in RequireUser, App receives the state changes and renders, but only after a flicker:
User required? true
I have required your user
I have the feeling what I am doing is an anti-pattern and I would be grateful for suggestions of a more elegant solution than flickering with setTimeout. Thanks!
My suggested answer is to add this to the App component:
componentDidMount() {
// setup listener for subsequent changes
alt.stores.UserStore.listen(this.onChange);
// grab the current state now that we're mounted
var userStoreState = alt.stores.UserStore.getState();
this.setState(userStoreState);
}
There is no way to avoid the double render. Your RequireUser component already performs two renders.
Initial render of RequireUser
componentDidMount() callback
an action is dispatched
UserStore receives the dispatched action and updates its state
change notification is emitted
RequireUser sets state based on the state change
Second render of RequireUser
But your codebase is still considered Flux, and indeed follows the pattern intended for React apps. Essentially, you have a loading state... a state where we don't actually know if we need to require a user or not. Depending on what UserActions.requireUser() does, this may or may not be desired.
You might consider a refactor
You can fix the double-render if you rewrite RequireUser as a view-only component. This means no listeners nor setting state internally. This component simply renders elements based on the props passed in. This is literally all your RequireUser component would be:
class RequireUser extends React.Component {
render() {
if (this.props.requireUser) {
return <div>I have required your user</div>;
}
return <div>I will require your user</div>;
}
}
You will then make your App component a controller-view. The listener is added here, and any changes to state are propagated downward by props. Now we can setup in the componentWillMount callback. This gives us the single render behavior.
class App extends React.Component {
(other lifecycle methods)
componentWillMount() {
if (!this.state.requireUser) {
UserActions.requireUser();
}
var userStoreState = alt.stores.UserStore.getState();
this.setState(userStoreState);
}
componentDidMount() {
(same as above)
}
render() {
return (
<div>
<div>User required? {this.state.requireUser + ''}</div>
<RequireUser requireUser={this.state.requireUser} />
</div>
);
}
}
Flux architecture and controller-views/views: https://facebook.github.io/flux/docs/overview.html#views-and-controller-views
Your components each only gets the states from your Store once - only during the construction of each components. This means that the states in your components will NOT be in sync with the states in the store
You need to set up a store listeners on your components upon mounting in order to retrieve a trigger from the store and the most up-to-date states. Use setState() to update the states inside the component so render() will be called again to render the up-to-date states
What about putting the store listener in the constructor? That worked for me.

Categories