Access another component's method and have defaultProps for it - reactjs

I'd like to access another component's userLogout function.
I have read this react-js-access-to-component-methods. However, the only way that seems to work for me is what follows.
Does anyone know another way that would be easier, shorter? My goal is to get all the logic out of Base component.
#azium pointed out that I'm using a derived class. The goal was initially to have access to static defaultProps so the problem was approached the wrong way.
class Funcs extends React.Component {
// this is the derived class way I was hoping to have (much cleaner)
static defaultProps = {
text: 'hello'
}
constructor(props) {
super(props);
}
userLogout() {
console.log('userLogout');
}
render() {
return null;
}
}
class Base extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
MyWidget = (el, refCb) => {
ReactDOM.render(<Funcs ref={refCb} />, el);
};
componentDidMount() {
this.MyWidget(document.getElementById('nothing'), widget => {
console.log('there you are...', widget);
this.setState({
widget
});
// works too
this.widget = widget
});
}
render() {
console.log('widget', this.state.widget, this.widget);
return <div id="nothing" />
}
}

Here's the solution to have defaultProps on a non derived class.
class Funcs {
constructor(props) {
this.props = Object.assign({}, this.defaultProps, props);
}
defaultProps = {
userLogout: {
onCompleted: () => this.props.nextRouter.pushRoute('home_v01', { lng: this.props.nextRouter.query.lng }),
onError: err => console.log('An error occured, ', err)
}
};
userLogout = () => {
LogoutMutation.commit({
environment: this.props.environment,
onCompleted: this.props.userLogout.onCompleted,
onError: this.props.userLogout.onError
});
};
}
class Base extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
funcs = new Funcs({
environment: this.props.relay.environment,
nextRouter: this.props.nextRouter,
userLogout: {
onCompleted: () => console.log('LOG OUT')
}
});
render() {
return <div onClick={this.funcs.userLogout}>Log out</div>
}
}

Related

React Hooks in class using timeout

I am quite new in React... I have page where window is showing with little delay..
it is made with Hooks:
export default function LoginPage() {
const [cardAnimaton, setCardAnimation] = React.useState('cardHidden');
setTimeout(function() {
setCardAnimation('');
}, 700);
<form>
<Card login className={classes[cardAnimaton]}>
Now I want to use classes in that page and I want to preserve the same effect..
So I am trying something like:
export default class LoginPage extends React.Component {
constructor(props) {
super(props);
this.state = {
cardHidden: true,
};
}
componentDidMount() {
this.timeout = setTimeout(() => {
this.setCardAnimation('');
}, 700);
}
setCardAnimation = () => {
this.cardAnimaton({ cardHidden: false });
};
I have no idea... got stuck there...
You just need to set your cardHidden: false inside componentDidMount and then you can add animation based on cardHidden state.
here is a working Demo which I used cardHidden state to show or hide different text on screen which you can use this method for adding different animation.
just click Run code snippet to see how it works
class LoginPage extends React.Component {
constructor(props) {
super(props);
this.state = {
cardHidden: true,
};
}
componentDidMount() {
this.timeout = setTimeout(() => {
this.setState({
cardHidden: false
});
}, 700);
}
render() {
if(this.state.cardHidden){
return <div>I'm Hidden</div>
} else {
return <div>Haha, I'm Visible</div>
}
}
}
const rootDiv = document.getElementById('root');
ReactDOM.render(<LoginPage />, rootDiv);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
In class-based component, you need to use this.setState to update the state.
export default class LoginPage extends React.Component {
constructor(props) {
super(props);
this.state = {
cardHidden: true,
};
}
componentDidMount() {
this.timeout = setTimeout(() => {
this.setState({ cardHidden: false });
}, 700);
}
...
check https://reactjs.org/docs/react-component.html#setstate

How to receive props only after state of parent has updated?

I'm trying to build a little weather widget, where the geolocation of the user is captured in one component and then passed onto a child component which fetches the weather data (based on the location) and then eventually renders an icon indicating the current weather conditions.
I'm passing the longitude and latitude state as props to my WeatherWidget. Unfortunately, the WeatherWidget also receives the initial state null. How I can I avoid that?
Thank you for your help!
class GetGeolocation extends Component{
constructor(){
super();
this.state = {
lngt: null,
latd: null
}
}
componentDidMount(){
this.getLocation()
}
getLocation = () => {
if(navigator.geolocation){
navigator.geolocation.getCurrentPosition(position => {
this.setState({lngt: position.coords.longitude.toFixed(4)});
this.setState({latd:position.coords.latitude.toFixed(4)});
}
);
};
}
render(){
return (
<>
<WeatherWidget lngt = {this.state.lngt} latd = {this.state.latd} />
</>
)
}
class WeatherWidget extends Component{
constructor(props){
super(props);
this.state = {
weather:[]
}
}
componentWillReceiveProps(nextProps){
this.getWeather(nextProps)
}
getWeather = (location) => {
console.log(location)
// The console logs twice:
// First:
//{lngt: "-12.3456", latd: null}
//Then, the correct values:
//{lngt: "-12.3456", latd: "78,9999"}
}
Don't use componentWillReceiveProps, that will be deprecated in later versions of React.
But also, you can just setup conditional logic in your life-cycle methods to determine what code to execute.
componentWillReceiveProps(nextProps){
//condition says if both value are truthy then run code.
if(nextProps.lngt && nextProps.latd){
this.getWeather(nextProps)
}
}
You can also use componentDidUpdate()
componentDidUpdate(){
//condition says if both value are truthy then run code.
if(this.props.lngt && this.props.latd){
this.getWeather(this.props)
}
}
One option is to conditionally render in the parent component:
class GetGeolocation extends React.Component {
constructor(props) {
super(props);
this.state = {
lngt: null,
latd: null
};
}
componentDidMount() {
this.getLocation();
}
getLocation = () => {
// Simulate the network request
setTimeout(() => this.setState({ lngt: 100 }), 1000);
setTimeout(() => this.setState({ latd: 100 }), 1000);
};
render() {
const { lngt, latd } = this.state;
if (!lngt || !latd) return null;
return <WeatherWidget lngt={lngt} latd={latd} />;
}
}
class WeatherWidget extends React.Component {
constructor(props) {
super(props);
this.state = {
weather: []
};
}
componentDidMount() {
this.getWeather(this.props);
}
getWeather = location => {
console.log(location);
};
render() {
return null;
}
}

React - Call wrapped component callback from higher order function

I have a higher order function that wraps the service calls. The data is streamed on a callback which I have to pass to the wrapped components. I have written the code below currently, where the child assigns handleChange to an empty object passed by the HOC. The wrapped component is a regular JS grid and hence I have to call the api to add data than pass it as a prop.
Is there a cleaner way of doing this?
function withSubscription(WrappedComponent) {
return class extends React.Component {
constructor(props) {
super(props);
this.handler = {};
}
componentDidMount() {
DataSource.addChangeListener(this.handleChange);
}
componentWillUnmount() {
DataSource.removeChangeListener(this.handleChange);
}
handleChange(row) {
if (typeof this.handler.handleChange === "function") {
this.handler.handleChange(row);
}
}
render() {
return <WrappedComponent serviceHandler={this.handler} {...this.props} />;
}
};
}
class MyGrid extends React.Component {
constructor(props) {
super(props);
if (props.serviceHandler !== undefined) {
props.serviceHandler.handleChange = this.handleChange.bind(this);
}
this.onReady = this.onReady.bind(this);
}
onReady(evt) {
this.gridApi = evt.api;
}
handleChange(row) {
this.gridApi.addRow(row);
}
render() {
return <NonReactGrid onReady={this.onReady} />;
}
}
const GridWithSubscription = withSubscription(MyGrid);
That wrapped component should be aware of handler.handleChange looks awkward.
If withSubscription can be limited to work with stateful components only, a component may expose changeHandler hook:
function withSubscription(WrappedComponent) {
return class extends React.Component {
...
wrappedRef = React.createRef();
handleChange = (row) => {
if (typeof this.wrappedRef.current.changeHandler === "function") {
this.wrappedRef.current.changeHandler(row);
}
}
render() {
return <WrappedComponent ref={this.wrappedRef}{...this.props} />;
}
};
}
class MyGrid extends React.Component {
changeHandler = (row) => {
...
}
}
const GridWithSubscription = withSubscription(MyGrid);
To work with stateful and stateless components withSubscription should be made more generalized to interact with wrapped component via props, i.e. register a callback:
function withSubscription(WrappedComponent) {
return class extends React.Component {
handleChange = (row) => {
if (typeof this.changeHandler === "function") {
this.changeHandler(row);
}
}
registerChangeHandler = (cb) => {
this.changeHandler = cb;
}
render() {
return <WrappedComponent
registerChangeHandler={this.registerChangeHandler}
{...this.props}
/>;
}
};
}
class MyGrid extends React.Component {
constructor(props) {
super(props);
props.registerChangeHandler(this.changeHandler);
}
changeHandler = (row) => {
...
}
}
In case the application already uses some form of event emitters like RxJS subjects, they can be used instead of handler.handleChange to interact between a parent and a child:
function withSubscription(WrappedComponent) {
return class extends React.Component {
changeEmitter = new Rx.Subject();
handleChange = (row) => {
this.changeEmitter.next(row);
}
render() {
return <WrappedComponent
changeEmitter={this.changeEmitter}
{...this.props}
/>;
}
};
}
class MyGrid extends React.Component {
constructor(props) {
super(props);
this.props.changeEmitter.subscribe(this.changeHandler);
}
componentWillUnmount() {
this.props.changeEmitter.unsubscribe();
}
changeHandler = (row) => {
...
}
}
Passing subjects/event emitters for this purpose is common in Angular because the dependency on RxJS is already imposed by the framework.

setState inside constructor not working properly: ReactJS

I'm trying to run the below code in React+Redux but am running into an unhandled
exception 'NodeInvocationException: Cannot read property 'showText' of
null TypeError: Cannot read property 'showText' of null'
import * as React from 'react';
import { NavMenu } from './NavMenu';
import { Component } from 'react';
export interface BlinkState
{
showText: boolean;
text: '';
}
type BlinkProps = BlinkState;
class Blink extends React.Component<BlinkProps, BlinkState> {
constructor(props: BlinkProps) {
super(props);
//this.state = { showText: true };
this.setState({ showText: true, text: props.text });
// Toggle the state every second
setInterval(() => {
this.setState(previousState => {
return { showText: !previousState.showText };
});
}, 1000);
}
render() {
let display = this.state.showText ? this.props.text : ' ';
return <div>{ display }</div>;
}
}
export class Layout extends React.Component<{}, {}> {
public render() {
return <div className='container-fluid'>
<Blink showText=false text='I love to blink' />
</div>;
}
}
I'm just trying to figure out how to render the Blink copmonent with the props passed in...
You missed the basic thing, use of constructor and setState, use of constructor is to initialize the state value and use of setState is to update the state value, so using setState inside `constructor doesn't makes any sense.
Better way will be, initialise the state in constructor and to run the time use componentDidMount lifecycle method, also don't forgot to stop the time before unmounting the component, to clear it use componentWillUnmount lifecycle method.
Write the component like this:
class Blink extends React.Component<BlinkProps, BlinkState> {
constructor(props: BlinkProps) {
super(props);
this.state = { showText: false };
}
componentDidMount(){
this.timer = setInterval(() => {
this.setState(previousState => {
return { showText: !previousState.showText };
});
}, 1000);
}
componentWillUnmount(){
clearInterval(this.timer)
}
render() {
let display = this.state.showText ? this.props.text : ' ';
return <div>{ display }</div>;
}
}
Working code:
class Blink extends React.Component {
constructor(props) {
super(props);
this.state = { showText: true, text: props.text };
}
componentDidMount(){
this.timer = setInterval(() => {
this.setState(prev => {
return { showText: !prev.showText };
});
}, 1000);
}
componentWillUnmount(){
clearTimer(this.timer)
}
render() {
let display = this.state.showText ? this.props.text : ' ';
return <div>Hello { display }</div>;
}
}
class Layout extends React.Component{
render() {
return <div className='container-fluid'>
<Blink text='I love to blink' />
</div>;
}
}
ReactDOM.render(<Layout/>, document.getElementById('app'))
<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='app'/>
You should not specify actions to be taken in the constructor or use setState there, constructor should be used to simply set an initial state.
Also you might need to update the state text since its being set based on props. Do it in the componentWillReceiveProps.
Also when you are using setInterval, make sure to clearInterval when the componentUnmounts
constructor(props: BlinkProps) {
super(props);
this.state = { showText: true, text: props.text };
}
componentWillReceiveProps(nextProps) {
this.setState({text: nextProps.text});
}
componentDidMount() {
// Toggle the state every second
this.interval = setInterval(() => {
this.setState(previousState => {
return { showText: !previousState.showText };
});
}, 1000);
}
componentWillUnmount() {
clearInterval(this.interval)
}

How to pass function as props in React?

I have functional component GetWeather which I want to pass result of GetLocation function as props based on which GetWetaher will do something i.e. another get request (in the example below it only renders its props). I think it has to happen inside ComponentDidMount, not sure how to do it
function GetLocation() {
axios.get('http://ipinfo.io')
.then((res) => {
return res.data.loc;
})
}
function GetWeather(props) {
//more code here, including another get request, based on props
return <h1>Location: {props.location}</h1>;
}
class LocalWeather extends Component {
componentDidMount() {
//???
}
render() {
return (
<div >
<GetWeather location={GetLocation}/> //???
</div>
);
}
}
Update: So based on suggestion from Damian below is working for me
function GetWeather(props) {
return <h3>Location: {props.location}</h3>;
}
class LocalWeather extends Component {
constructor(props) {
super(props);
this.state = {
location: []
};
}
getLocation() {
axios.get('http://ipinfo.io')
.then((res) => {
this.setState({location:res.data.loc});
})
}
componentDidMount() {
this.getLocation();
}
render() {
return (
<div >
<GetWeather location={this.state.location}/>
</div>
);
}
}
You can do it alternatively also
constructor() {
super();
this.state = {
Location:[]
}
}
function GetLocation() {
axios.get('http://ipinfo.io').then((res) => {
this.setState ({
Location:res.data.loc;
});
});
}
function GetWeather(props) {
return <h1>Location: {this.props.location}</h1>;
}
class LocalWeather extends Component {
componentDidMount() {
//code
}
render() {
return (
<div >
<GetWeather location={this.GetLocation.bind(this)}/>
</div>
);
}
}

Resources