I am using React v0.14.8. I tried to call the fetchData function from another component. Here is my code:
export default class TagUtils extends React.Component {
deleteTag = () => {
Tags.deleteTag(this.props.tag).then(function(response){
if(response.message === 'tag successfully deleted')
Sidebar.fetchData();
});
}
// other codes
And:
export default class Sidebar extends React.Component {
fetchData() {
Tags.getTags().done((response) => {
this.setState({tags: response.tags || [], loaded: true});
});
}
//other codes
When I called deleteTag, I got this error in my console:
TypeError: _SidebarJsx2.default.fetchData is not a function
You can't call Sidebar.fetchData because fetchData is not a static member of Sidebar, it is an instance member. This means you need an instance of Sidebar to call fetchData on, for example new Sidebar().fetchData(). Of course, this is not how a React component is supposed to be used, and it would not set state on all other Sidebar instances, so it wouldn't be meaningful.
What you want to do is pass a callback to your TagUtils component:
export default class TagUtils extends React.Component {
deleteTag = () => {
Tags.deleteTag(this.props.tag).then((response) => {
if(response.message === 'tag successfully deleted')
this.props.onDeleteTag();
});
}
}
export default class Sidebar extends React.Component {
fetchData() {
Tags.getTags().done((response) => {
this.setState({tags: response.tags || [], loaded: true});
});
}
render() {
return (
{ this.state.tags.map((tag) =>
<TagUtils tag={tag} onDeleteTag={this.fetchData} />) }
);
}
}
If you have to thread this callback through several layers of components that's okay, that's typical in React. However, if you find yourself passing a lot of stuff down props through many component layers that seem out of place, or trying to reconcile changes across large horizontal spaces in your app, this is a primary use-case for things like Flux and Redux.
Related
I am very new to react.js and I have been working on a component class (child) that has functions and a single state object and my end goal is to use this class in a parent class so it, in turn, can call its functions and update the state.
The problem I have been running into is that:
I wasn't aware of a component's lifecycle, and
I come from a heavy C# background
Meaning: I have been treating these component classes like I would any C# class instead of JavaScript. I know that now.
But I need help evaluating my approach and solving this issue I keep seeing:
This is my child Class component
import React, { Component } from 'react';
import axios from 'axios';
export default class ClassB extends Component {
constructor() {
super();
console.log("ClassB constructor got called");
this.state = {
users: [{ name: '', email: '' }]
};
}
getUsers() {
let URL = "https://localhost:5001/api/FooController/FooAction"
let myParam = 100;
axios.get(URL,
{
params: { myParam }
})
.then(response => {
// handle logic here...
}).catch(function (error) {
console.log('What happened? ' + error.response.data);
});
}
addUserData(name, email) {
this.setState(this.state, { users: [name, email] });
}
componentDidMount() {
console.log("ClassB componentDidMount got called");
}
render() {
console.log("ClassB render got called");
return ( null )
}
}
And in my parent class (Home.js) I am instantiating the child class (ClassB.js) and using its instance as such:
import React, { Component } from 'react';
import ClassB from './ClassB'
import ClassC from './ClassC'
const classBComponent = new ClassB();
export class Home extends Component {
static displayName = Home.name;
constructor() {
super();
}
componentDidMount() {
this.timerID = setInterval(() => {
classBComponent.getUserValues();
}, 3000);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
render() {
myComponent.render();
return (
<div className="container">
<h1>My Other Component:</h1>
<div className="row">
<div className="col-sm">
<ClassC name={[{ component: classBComponent, info: ['John', 'john123#123.com'] }]} />
</div>
</div>
</div>
);
}
}
In this parent class I intend to call the "getUserValues() methods from the ClassB component. I also have another child component (ClassC), which is a functional component, and I want to pass the instantiated ClassB component so it can access its functions and states as well. But, in ClassC component, when I call "addUserData()" method it gives me the error I pasted above (see image).
Here is how I have my ClassC set up:
import React, { useEffect } from 'react';
import axios from 'axios';
const ClassC = (props) => {
// variables
let user = props.name[0].info[0];
let email = props.name[0].info[1];
// component
const component = props.name[0].component;
// renders component
function componentMount() {
component.addSimModelNodeInfo(user, email);
}
// leaves the component
function componentUnmount() {
}
useEffect(() => {
componentMount();
return () => {
componentUnmount();
}
}, [])
return (
<div className="card shadow-sm p-3 mb-5 bg-white rounded">
<div className="card-header">
<h5>{name}</h5>
<h5>{email}</h5>
</div>
</div>
);
}
export default ClassC;
I mentioned earlier how I didn't have a solid grasp on components' lifecycles. I placed those console.logs in ClassB only to realize that the only method getting called is the constructor. The componentDidMount() function never gets called and neither does the render(). Why is that? I know its linked to that error which is why my ClassB component never gets "mounted". What am I doing wrong? Many thanks in advance.
Here's an example of calling a parent component function from a child component. I'm using functional components for brevity, but this is totally possible with class-based as well.
If you're not familiar with functional components, you can consider props to be like this.props, and the function itself is similar to the render() method in classful components. There's more to it, but for this small example that should help you if you need to translate.
const ChildComponent = (props) => <button onClick={props.onClick} />
const ParentComponent = () => {
const onClick = () => {
console.log("Click Clack");
}
return <ChildComponent onClick={onClick} />;
};
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
I'm trying to access functions inside a class based component which can be used throughout the project. The reason I'm thinking class based is because these request/ functions require an init() method to be called before accessing such data every time. For example:
SharedSDKFile .js
import Facebook from 'facebook-sdk';
class SharedSDKFile extends Component {
constructor() {
Facebook.init({// init some stuff})
}
async user() {
return Facebook.getUser()
}
render() {
return(// ????????????????)
}
}
// *****************************************************
Dashboard.js
// *****************************************************
import Facebook from '../{path}/SharedSDKFile'
const dashboard = () => {
let [person,setPerson] = useState()
// cool function to get and set Users
let user = getUser();
setPerson(user)
// End of cool function
}
I even tried structuring it with a different approach just exporting functions
SharedSDKFile.js
async init() {
// init stuff
}
export const getUser = async(data) => {
init()
// get user
}
// *****************************************************
Dashboard.js
// *****************************************************
import {getUser}from '../{path}/SharedSDKFile'
const dashboard = () => {
let [person,setPerson] = useState()
// cool function to get and set Users
let user = getUser();
setPerson(user)
// End of cool function
}
While a file that exports your function works, the state disappears on reload/ refresh.
Perhaps there is a better solution to this and I'm overthinking it. I have considered redux or localstate, but I will have several functions inside the sharedSDKFile.js which will require several action and reducers...
I am trying to prevent invoking multiple init() and redundancy if I am to import, for example, the FacebookSDK in every file that needs it.
I like using a context singleton approach to isolate external services that can be used app-wide, which may or may not suit what you want to do. It uses context providers/consumers rather than things like Redux.
This isn't the complete picture but hopefully it provides some idea of how the approach might work for you:
FacebookContext.js (or AWSContext.js, or... any specific service)
import React, { Component, createContext } from "react";
import Facebook from "facebook-sdk";
export const FacebookContext = createContext({});
class FacebookProvider extends Component {
state = {
user: null,
// whatever else you need to expose to calling components, like
// lastLoggedIn: null,
// verified: false,
// ...
};
componentDidMount() {
Facebook.init({
// init some stuff
})
}
setUser = user => {
this.setState({
user
});
}
// whatever other methods/data you want this class to expose
render() {
return (
<FacebookContext.Provider
value={{
user: this.state.user // available with FacebookContext.user
// other state values
//
setUser: this.setUser // available with FacebookContext.setUser
// other class methods
}}
>
{this.props.children}
</FacebookContext.Provider>
);
}
}
export default FacebookProvider;
Add the Provider to your top-line app file, something like this in e.g. App.js:
import FacebookProvider from "/path/to/FacebookContext";
// ...
class App extends React.Component {
render() {
return (
<FacebookProvider>
{yourAppRenderStuff}
</FacebookProvider>
);
}
}
Add the Consumer to any calling components:
import { FacebookContext } from "/path/to/FacebookContext";
class SomethingComponent extends Component {
componentDidMount() {
const { facebookContext } = this.props;
facebookContext.setUser(`some user`);// available in other components
console.log(facebookContext.user);// broadcasted to other components
}
render() {
return (
<></>
);
}
}
const Something = () => (
<FacebookContext.Consumer>
{facebookContext => (
<SomethingComponent facebookContext={facebookContext} />
)}
</FacebookContext.Consumer>
);
export default Something;
I have a scenario where I'm trying to update a React/Redux state from a function that's placed on the Window. The function on the window is unable to access the function that's in the React component. Any idea how to bind that function in this kind of setup? This snippet just has a console log where the Redux call would go.
class MyComponent extends Component {
updateRedux = a => {
console.log(a)
}
componentDidMount() {
window.windowFunction = function(a) {
this.updateRedux(a)
}
}
render() {
return (
<Stuff />
)
}
}
this is not accessible inside your function, you need to bind it.
Try with:
class MyComponent extends Component {
updateRedux = a => {
console.log(a)
}
componentDidMount() {
window.windowFunction = function(a) {
this.updateRedux(a)
}.bind(this)
}
render() {
return (
<Stuff />
)
}
}
if you meant that you want to update Redux state with some action (this is the only way to update Redux state by design), then you need to make this action and its functions available to your Component with connect(mapStateToProps, mapDispatchToProps)(Component)
One of the comments above about converting the windowFunction to an arrow function resolved the issue. Thanks!
class MyComponent extends Component {
updateRedux = a => {
console.log(a)
}
componentDidMount() {
window.windowFunction = a => {
this.updateRedux(a)
}.bind(this)
}
render() {
return (
<Stuff />
)
}
}
What you could do is separate the concerns using a presenter and a connected
component, using react-redux. I am assuming you know of this library, comment
if you need more details.
// Simple "presenter", the getComponentData is used to get the data for the
// redux store.
class MyComponentPresenter extends Component {
// returns data for redux
getComponentData () {}
componentDidMount() {
this.props.updateRedux(this); // update Redux
}
render() {
return (
<Stuff />
)
}
}
// This component has the exact same interface, but comes with a updateRedux
// props which automatically dispatches an action
export const MyComponent = connect(null, {
updateRedux(componentInstance) {
return {
type: "updateRedux"
};
}
});
// in the reducer
//
function reducer (state, action) {
switch (action.type) {
case "updateRedux":
return ...
}
}
No more need for globally available function (which in your example is redefined for each instance of MyComponents which is probably not what you want).
Imagine I have some "page" component, which needs to ask for data from a server. The data it requests will depend on whether or not the current user is authenticated. Further, in the event of a login, the page will want to reload the data. My question is, how can I accomplish something like this using HOCs rather than inheritance?
To illustrate the problem, I'll demonstrate a solution using inheritance. The program will have the following objects. I'll leave out the boilerplate code.
session: an EventEmitter that emits start when the session changes (either a login or a log out).
Page: the superclass that all pages inherit from
MyPage: the subclass of Page in this example
API: will be an API class for retrieving data from the server
Here's the code:
// Page superclass
class Page extends React.Component {
componentWillMount() {
session.on("start", this.loadData);
this.loadData();
}
loadData() {
// this method is overwritten in subclasses
}
}
// MyPage subclass
class MyPage extends Page {
loadData() {
if(session.isAuthenticated()) {
API.loadPrivateData();
} else {
API.loadPublicData();
}
}
}
Here's a solution that uses an HOC, but seems less elegant than inheritance. It still requires that every "subclass" page have a method loadData, and it requires that method to be called in every "subclass's" componentWillMount.
// Page HOC
function Page(WrappedComponent) {
return class EnhancedPage extends React.Component {
componentWillMount() {
session.on("start", this.loadData);
// this._page.loadData() will fail here
// since this._page is undefined until rendering finishes
}
loadData() {
this._page.loadData();
}
render() {
return <WrappedComponent {...props} ref={(e) => { this._page = e; }} />
}
}
}
// MyPage
class MyPage extends React.Component {
componentWillMount() {
this.loadData();
}
loadData() {
if(session.isAuthenticated()) {
API.loadPrivateData();
} else {
API.loadPublicData();
}
}
}
const component = Page(MyPage)
// what would make sense here is to have a method something like
// const component = Page(MyPage, () => MyPage.loadData())
// but then the MyPage.loadData logic would need to be defined
// elsewhere
This pattern will happen often: I'll want to load some data, then reload when the session changes. I'd like to understand the "react" way of accomplishing the same.
EDIT: I am not trying to pass a username or "loggedIn" flag through the HOC. That is to say something like <WrappedComponent isLoggedIn={session.isAuthenticated()} {...props} /> won't cut it here. Tying the API logic to props requires that I check for changes in MyPage.componentWillUpdate().
When using a HOC you shouldn't place the loadData function on the wrapped component. Instead pass the function as a parameter to the HOC constructor.
Something like this might work for you. The sessionHoc function takes a callback function which'll be called every time the session state changes. Its result will be passed to WrappedComponent as a data prop.
function sessionHoc(onSessionChange) {
return function (WrappedComponent) {
return class extends React.Component {
constructor(props) {
super(props);
this.state = {
data: null,
};
session.on('start', this.handleSessionChange.bind(this));
}
handleSessionChange() {
this.setState({
data: onSessionChange(),
});
}
render() {
return <WrappedComponent data={data} {...this.props} />
}
};
};
}
class MyPage extends React.Component {
render() {
// Just access this.props.data here!
}
}
const EnhancedPage = sessionHoc(function () {
if (session.isAuthenticated()) {
return API.loadPrivateData();
} else {
return API.loadPublicData();
}
})(MyPage);
Hopefully this helped! :)