Reactjs: setState from outside of component - javascript

App.js
import React from 'react';
import './App.css'
import Tools from './components/class/Tools'
import Loading from './components/inc/Loading'
export default class App extends React.Component {
componentDidMount() {
Tools.showLoading(); // or new Tools();
}
render() {
return (
<div className="App">
<Loading />
</div>
)
}
}
Loading.js:
import React from 'react'
export default class Loading extends React.Component {
constructor(props) {
super(props)
this.state = {
display: 'none'
}
}
render() {
return (
<div className="loading" style={{display: this.state.display}}>
<span></span>
</div>
)
}
}
Tools.js
export default class Tools extends React.Component {
static showLoading(){ // or non-static
Loading.setState ...
}
}
I want change display state from outside of Loading component.
I use Loading in whole my project and I want create function for handle it.
Example for another use:
function xxx(){
Tools.showLoading(); // or new Tools();
}
Or:
<span onClick={Tools.showLoading(); // or new Tools();}></span>
Actually, I want create only one function to manage and handle display of Loading.

In Tools.js
let loadingStateSetter = null
export function setLoadingStateSetter(setter) {
loadingStateSetter = setter
return () => loadingStateSetter = null
}
export function setLoadingState(value) {
if (loadingStateSetter !== null) loadingStateSetter(value)
}
In Loading.js:
import { setLoadingStateSetter } from './Tools.js'
export default class Loading extends React.Component {
constructor(props) {
super(props)
this.state = {
display: 'none'
}
}
render() {
return (
<div className="loading" style={{display: this.state.display}}>
<span></span>
</div>
)
}
componentDidMount() {
this.removeStateSetter = setLoadStateSetter((value) => {
this.setState((state) => ({
...state,
display: value,
})
})
}
componentWillUnmount() {
this.removeStateSetter()
}
}
Usage:
import { setLoadingState } from './Tools.js'
function xxx(){
setLoadingState('some value')
}

While you can easily expose a setState function externally, it acts just like any other function, its not usually a good idea. You should instead consider rewriting your Loading component to use the property object to tell it if its loading and track the loading state higher up the component tree where it is accessible by things that would want to change its status.

I think you can using redux as store manager global state
https://redux.js.org/
another way pass it through props and handle it at parent component

Related

React JSON mapped data. Trying to pass a setState variable from mapped function will not get the latest state on render

I'm working on a React SPA and trying to render JSON data with a filter. I have several containers that each have a class value. When clicking on a container, I am trying to pass a value to a setState. When stepping out of the map function, however, I cannot get the new state change to update.
I'm assuming that the render state isn't getting updated for the state.
import React, { Component } from "react";
import ListData from "../data/list.json";
class Levels extends Component {
constructor(props) {
super(props);
this.state = { newFilter: "" };
this.onClick = this.setFilter.bind(this);
}
setFilter = e => {
console.log(e); //This returns the correct class.
this.setState({ newFilter: e }); //This will not update
};
render() {
console.log(this.state.newFilter); //This just returns the initial state of ''
const activeTab = this.props.keyNumber;
let levelFilter = ListData.filter(i => {
return i.level === activeTab;
});
let renderLevel = levelFilter.map((detail, i) => {
let short_desc;
if ((detail.short || []).length === 0) {
short_desc = "";
} else {
short_desc = <p>{detail.short}</p>;
}
return (
<div
className={`kr_sItem ${detail.class}`}
data-value={detail.class}
onClick={() => this.onClick(detail.class)}
value={detail.class}
key={i}
>
<h1>{detail.title}</h1>
{short_desc}
<img src={`${detail.img}`} />
</div>
);
});
console.log(this.state.newFilter);
return (
<div id="kr_app_wrapper">
<div id="lOneWrapper">{renderLevel}</div>
<div id="lTwoWrapper"></div>
</div>
);
}
}
export default Levels;
Here is the code calling in the Component:
import React, {Component} from 'react';
import Levels from './components/levels2';
import {
Route,
NavLink,
HashRouter
} from "react-router-dom";
import LevelTwo from "./levelTwo";
class LevelOne extends Component{
render(){
return(
<div id="lOneWrapper">
<NavLink to='/Level2'><Levels keyNumber={1} /></NavLink>
</div>
)
}
}
export default LevelOne;
Edit: As pointed out in the comments, binding an arrow function is pointless but it wouldn't cause the error here, so there must be something else going on in some other part of your code.
The problem is you are trying to bind an arrow function that doesn't have an implicit this. Either call setFilter directly (no need to bind this with arrow functions) or change your setFilter function to a regular function:
class Levels extends Component{
constructor(props){
super(props);
this.state = {newFilter: ''};
this.onClick = this.setFilter.bind(this);
}
setFilter(e) {
console.log(e); //This returns the correct class.
this.setState({newFilter: e}); //This will not update
}

Running method of component child from this.props.children array

import React from "react";
import ReactDOM from "react-dom";
class NestedComponent extends React.Component {
constructor(props) {
super(props);
this.childMethod = this.childMethod.bind(this);
}
childMethod() {
alert("Child method one ran");
}
render() {
return <div>NestedComponent</div>;
}
}
class NestedComponentTwo extends React.Component {
constructor(props) {
super(props);
this.childMethod = this.childMethod.bind(this);
}
childMethod() {
alert("Child method two ran");
}
render() {
return <div>NestedComponentTwo</div>;
}
}
class WrappingComponent extends React.Component {
constructor(props) {
super(props);
this.runMethod = this.runMethod.bind(this);
}
runMethod() {
let child = this.props.children[0];
/** Always returns as undefined */
//if (typeof child.childMethod == "function") {
// child.childMethod();
//}
/**
* EDIT: Close, however the this binding seems to not be working. I can however provide the childs props to the childMethod and work with that.
*/
if(typeof child.type.prototype.childMethod == "funciton"){
child.type.prototype.childMethod();
}
}
render() {
return (
<div>
{this.props.children}
<button onClick={this.runMethod}>run</button>
</div>
);
}
}
const App = ({}) => {
return (
<div>
<WrappingComponent>
<NestedComponent />
<NestedComponentTwo />
</WrappingComponent>
</div>
);
};
if (document.getElementById("example")) {
ReactDOM.render(<App />, document.getElementById("example"));
}
So the goal is to have optional methods attached to a nested component that can execute from the wrapping component, almost like an event emmiter. For some reason though, the method that exists on the child component claims not to exist. However whenever I log the child component pulled from the array of the this.props.children the prototype has the method listed.
Am I missing a special way to access methods of children components through a methods variable perhaps?
Found the variable I can use to access it. If anyone has any more insight into this, or reasons why what I am doing is poor practice please let me know.
Editing the question where this is needed, but the item below is accessing the function of the child:
child.type.prototype.childMethod
Does not appear to maintain the this binding. Passing props down does work however.
You should manage all of this logic in the top level component (the App component)
class NestedComponent extends React.Component {
constructor(props) {
super(props);
this.childMethod = this.childMethod.bind(this);
}
childMethod() {
alert("Child method one ran");
}
render() {
return <div>NestedComponent</div>;
}
}
class NestedComponentTwo extends React.Component {
constructor(props) {
super(props);
this.childMethod = this.childMethod.bind(this);
}
childMethod() {
alert("Child method two ran");
}
render() {
return <div>NestedComponentTwo</div>;
}
}
class WrappingComponent extends React.Component {
render() {
return (
<div>
{this.props.children}
<button onClick={this.props.onClick}>run</button>
</div>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.runMethod = this.runMethod.bind(this);
}
runMethod() {
if (this.nestedComponent) {
this.nestedComponent.childMethod();
}
}
render() {
return (
<div>
<WrappingComponent onClick={this.runMethod}>
<NestedComponent ref={el => this.nestedComponent = el} />
<NestedComponentTwo />
</WrappingComponent>
</div>
);
}
};
if (document.getElementById("example")) {
ReactDOM.render(<App />, document.getElementById("example"));
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="example"></div>
Moreover ref with string attribute is deprecated https://reactjs.org/docs/refs-and-the-dom.html#legacy-api-string-refs

losing react component ref after I Wrapped it by injectIntl

I have an issue with getting the ref to the func in React components after I am wrapping it with injectIntl.
basically what I need is to get access to a func in the component by ref
here is what I am doing
class MainContainer extends React.Component {
constructor(props) {
super(props);
}
getSamples(){
return sth
}
render() {
return (<div>this.props.sth</div>)
}
export default injectIntl(MainContainer )
it possible to get the ref to the MainContainer after wrapped it with injectIntl?
The withRef option should be passed.
export default injectIntl(MainContainer,{ withRef: true })
The MainContainer wrapper component instance can be retrieved using
<MainContainer ref={c => { this.container = c; }} />
The wrapped component instance can be retrieved using
this.container.getWrappedInstance();
injectIntl has a forwardRef property which causes it to pass down ref to the wrapped component.
// MyComponent.jsx
// ...
export default injectIntl(MyComponent, {forwardRef: true});
// MyApp.js
import MyComponent from 'MyComponent';
class MyApp {
render() {
this.myComponentRef = React.createRef();
return <MyComponent ref={ref} />;
}
}
reference

Add methods to Higher Order Components in React

From what I understand, HOCs in ReactJS add props to your decorated component, but I want to add methods that can also act on the state.
As an example, I generally never call this.setState() without checking this.isMounted() first. In essence, I want:
export default ComposedComponent => class BaseComponent extends React.Component {
static displayName = "BaseComponent";
constructor(props) {
super(props);
}
//------> I want this method to be available to any ComposedComponent
//------> And it has to act upon the state of ComposedComponent
updateState(obj) {
if (this.isMounted() && obj) {
this.setState(obj);
}
}
render() {
return (
<ComposedComponent {...this.props} {...this.state} />
)
}
}
Say I want to decorate my component Home. So I'd just return it as export default BaseComponent(Home).
But this.updateState() is not available inside Home class. How do I solve this?
Okay, I figured it out. I had spent too much time on this, so I hope this answer could help somebody out as well. Short answer: add the method in your decorator to props, then bind it in your decorated class' constructor.
Here is the code:
export default ComposedComponent => class BaseComponent extends React.Component {
static displayName = "BaseComponent";
constructor(props) {
super(props);
// Note how I am adding this to state
// This will be passed as a prop to your composed component
this.state = {
updateState: this.updateState
}
}
updateState(obj) {
this.setState(obj);
}
render() {
return (
<ComposedComponent {...this.props} {...this.state} />
)
}
}
And here is an example of a class that would use it (I'm using ES7 for simplicity):
#BaseComponent
class Home extends React.Component {
static displayeName = 'Home';
constructor(props) {
super(props);
// And here I am binding to it
this.updateState = this.props.updateState.bind(this);
}
render() {
return (
<div>Hi</div>
)
}
}

ReactJS: Can not type into input field

I am starting to learn react and download and follow any tutorials in internet. I am trying to build friend list.
I have tree components,
friends_container:
import React from 'react';
import AddFriend from './add_friend.jsx'
import ShowList from './show_list.jsx'
class FriendsContainer extends React.Component {
constructor() {
this.state = {
friends: ['Jake Lingwall', 'Murphy Randall', 'Merrick Christensen']
}
}
addFriend(friend) {
this.setState({
friends: this.state.friends.concat([friend])
});
}
render() {
return (
<div>
<h3> Add your friend to friendslist </h3>
<AddFriend addNew={this.addFriend}/>
<ShowList names={this.state.friends}/>
</div>
)
}
}
export default FriendsContainer;
add_friend:
import React from 'react';
class AddFriend extends React.Component {
constructor() {
this.state = {newFriend: ''};
}
updateNewFriend(e) {
this.setState({
newFriend: e.target.value
})
}
handleAddNew() {
this.props.addNew(this.state.newFriend);
this.setState({
newFriend: ''
});
}
render() {
return (
<div>
<input type="text" value={this.state.newFriend} onChange={this.updateNewFriend}/>
<button onClick={this.handleAddNew}>Add Friend</button>
</div>
)
}
}
AddFriend.propTypes = { addNew: React.PropTypes.func.isRequired };
export default AddFriend;
show_list:
import React from 'react';
class ShowList extends React.Component {
render() {
var listItems = this.props.names.map((f, i) => <li key={i}>{f}</li>);
return (
<div>
<h3>Friends</h3>
<ul>
{listItems}
</ul>
</div>
)
}
}
ShowList.defaultProps = { names: [] };
export default ShowList;
and app.jsx
import React from 'react';
import FriendsContainer from './components/friends_container.jsx';
window.React = React;
React.render(<FriendsContainer />, document.body);
as you can see on the code, I am using es6 and babel as transcompiler.
My problem, I can not type any letters into the input field to add new friend into friends list. What am I doing wrong?
In the context of your updateNewFriend method, this refers to the window object and not the current component instance. You need to bind the method before passing it as the onChange event handler. See Function::bind.
You have two options:
class AddFriend extends React.Component {
constructor() {
// ...
this.updateNewFriend = this.updateNewFriend.bind(this);
this.handleAddNew = this.handleAddNew.bind(this);
}
}
or
class AddFriend extends React.Component {
render() {
return (
<div>
<input type="text" value={this.state.newFriend} onChange={this.updateNewFriend.bind(this)}/>
<button onClick={this.handleAddNew.bind(this)}>Add Friend</button>
</div>
)
}
}
Keep in mind that Function::bind returns a new function, so binding in render creates a function every time your component is rendered, though the performance impact is negligible.

Categories