Best practices for using React refs to call child function - javascript

I'm hoping for some clarity on the use of React refs for calling a child function. I have a Parent component that's a toolbar with a few buttons on it, and in the child component I have access to a library's export functionality. I'd like to call this export function on a button click in the parent component. Currently I'm using React refs to accomplish this:
Parent.js [ref]
class Parent extends React.Component {
onExportClick = () => {
this.childRef.export();
}
render() {
return (
<div>
<button onClick={this.onExportClick} />Export</button>
<Child ref={(node) => this.childRef = node;} />
</div>
)
}
}
Child.js [ref]
class Child extends React.Component {
export() {
this.api.exportData();
}
render() {
<ExternalLibComponent
api={(api) => this.api = api}
/>
}
}
This solution works fine, but I've seen a lot of disagreement on if this is the best practice. React's official doc on refs says that we should "avoid using refs for anything that can be done declaratively". In a discussion post for a similar question, Ben Alpert of the React Team says that "refs are designed for exactly this use case" but usually you should try to do it declaratively by passing a prop down.
Here's how I would do this declaratively without ref:
Parent.js [declarative]
class Parent extends React.Component {
onExportClick = () => {
// Set to trigger props change in child
this.setState({
shouldExport: true,
});
// Toggle back to false to ensure child doesn't keep
// calling export on subsequent props changes
// ?? this doesn't seem right
this.setState({
shouldExport: false,
});
}
render() {
return (
<div>
<button onClick={this.onExportClick} />Export</button>
<Child shouldExport={this.state.shouldExport}/>
</div>
)
}
}
Child.js [declarative]
class Child extends React.Component {
componentWillReceiveProps(nextProps) {
if (nextProps.shouldExport) {
this.export();
}
}
export() {
this.api.exportData();
}
render() {
<ExternalLibComponent
api={(api) => this.api = api}
/>
}
}
Although refs are seen as an "escape hatch" for this problem, this declarative solution seems a little hacky, and not any better than using refs. Should I continue to use refs to solve this problem? Or should I go with the somewhat hacky declarative approach?

You don't need to set the shouldExport back to false, you could instead detect the change:
componentWillReceiveProps(nextProps) {
if (nextProps.shouldExport !== this.props.shouldExport) {
this.export();
}
}
Then every toggle of the shouldExport would cause exactly one export. This however looks weird, I'd use a number that I'd increment:
componentWillReceiveProps(nextProps) {
if (nextProps.exportCount > this.props.exportCount) {
this.export();
}
}

I ran into the same problem in many occasions now, and since the React team doesn't encourage it i'll be using the props method for later development, but the problem is sometimes you want to return a value to the parent component, sometimes you need to check the child's state to decide whether to trigger an event or not, therefore refs method will always be my last haven, i suggest you do the same

Related

New to React - How do I go about fixing/improving this component?

I'm new to the React framework and recently wrote the below code but have been asked to understand what is wrong with it and how I could improve/fix this component.
Please could someone advise a better way of structuring this component and why this approach? Thanks!
class App extends React.Component {  
  constructor(props) {
    super(props);
    this.state = {
      name: this.props.name || 'Anonymous'
    }
  }    
  render() {
    return (
      <p>Hello {this.state.name}</p>
    );  
  }
}
Unless name is supposed to change at some point, your component does not need to be stateful. Simply use the name prop, and use defaultProps to provide the default value:
class App extends React.Component {
render() {
return (
<p>Hello {this.props.name}</p>
)
}
}
App.defaultProps = {name: 'Anonymous'}
You actually don't need to use a class for such a simple component:
function App() {
return (
<p>Hello {this.props.name}</p>
)
}
App.defaultProps = {name: 'Anonymous'}
See the Function and Class Components section for more information about functional vs classes component.
The construction of the state cannot be done that way. You must use componentDidMount() and setState(),
componentDidMount() {
this.setState({
name: this.props.name
});
}
Do you receive some content from an input, or you just want to display this state? If you just wanna display this state, you can just change constructor block with state = { name: 'Anonimus'} and call it in your jsx just as you did.

React.js transferring prop state from sibling to a different sibling?

I'm extremely new to react and javascript so I'm sorry if I'm asking this wrong.
This is for a group project which means I most likely don't understand the project 100%
The older sibling has this nice prop that I want to use:
```
export default class OlderSibling extends Component {
state = {
currentCity: city //(that was gathered from a bunch of other steps)
};
render() {
return(
)
}
}
```
The parent file doesn't have this prop, but it does have its own stuff. I do not care for these other props though.
```
class Parent extends Component {
constructor(props) {
super(props)
this.state = {
flower: 'red'
}
}
render() {
return (
<OlderSibling/>
<YoungerSibling/>
)
}
}
```
The younger sibling (the one that wants current city) has a bunch of this.state properties that I do not want to share with others but just want the older sibling's stuff (I guess like what younger siblings normally do).
```
export class YoungerSibling extends Component {
constructor(props) {
super(props);
this.state = {
title: '',
description: []
}
}
render() {
return(
)
}
}
```
Just in case I wasn't clear, younger sibling just wants older sibling's this.state: currentCity that older Sibling worked so hard to gather.
I know I didn't put the code completely, but if you want to critique it anyway, please do! I am still learning and I welcome every bit of feedback!
I looked up ways to do this, but they're all about transferring parent to child which is not what I want. I also read that there was Redux that could handle this?? I don't know if my fellow groupmates are interested in that just yet.
Thank you for your time in reading this!
EDIT:
[ SOLVED ]
I just want to edit and say thank you to #soupette, #Liam, #[Denys Kotsur], and #Tomasz for helping me to understand react a bit more. I realize that this post was very much a spoon feeding request and you all helped away. Thank you!
Also, just in case anybody else ran into this issue, don't forget to call it on Younger Sibling as this.props.currentCity .
As the previous answer suggests lift the state up I also prefer to use it
Here's an example
You can do something like:
class Parent extends Component {
state = {
flower: 'red'
currentCity: '',
};
updateCurrentCity = (currentCity) => this.setState({ currentCity });
render() {
return (
<OlderSibling updateCurrentCity={this.updateCurrentCity} />
<YoungerSibling city={this.state.currentCity} />
);
}
}
then in your OlderSibling you can update the parent's state with a function:
export default class OlderSibling extends Component {
state = {
currentCity: city //(that was gathered from a bunch of other steps)
};
componentDidUpdate(prevProps, prevState) {
if (prevState.currentCity !== this.state.currentCity) {
this.props.updateCurrentCity(this.state.currentCity);
}
}
render() {
return(
);
}
}
You should create Parent component for these two ones and keep state there.
Also, you should pass it into two children (YoungerSibling and OlderSibling) as a prop and add inverse data flow for OlderSibling so when city is changed in the OlderSibling then ParentSibling should know about this.
For example:
Parent.jsx
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
currentCity: ''
}
}
currentCityChangeHandler = city => {
this.setState({currentCity: city});
};
render() {
const { currentCity } = this.state;
return(
...
<OlderSibling
currentCity={currentCity}
onCurrentCityChange={this.currentCityChangeHandler}
/>
<YoungerSibling currentCity={currentCity} />
...
)
}
}
OlderSibling.jsx
class OlderSibling extends React.Component {
...
// Somewhere when new city is coming from
// You should pass it into the callback
newCityComingFunction() {
const city = 'New York';
// Pass the new value of city into common `Parent` component
this.props.onCurrentCityChange(city);
}
...
}
So in this case it will allow you to use this param in both cases and it will be keep updated.
In addition, you should use this.props.currentCity in the OlderSibling instead of using this.state.currentCity in this component because it's value is moved into Parent's component state.
Hope it will helps.
You got two choices.
use external state management library like redux.
lift the state up - which means Parent component will hold currentCity in the state.
There could be 3rd option of using contex API but I'm not sure how to do it here.

Blacklist React components

Is there a way to define a function to hook before each component in my app is mounted?
The idea is that if a component is blacklisted it doesn't mount at all.
The solution must leave the components unmodified for backward compatibility and should run in production (so rewire and other testing tools are probably off the table but open to suggestions :) )
Example
//something like this...
ReactDOM.beforeEachComponentMount( (component, action) => {
if(isBlacklisted(component)){
action.cancelMountComponent();
}
}
Could you write a simple Babel plugin that transforms blacklisted components to a noop functional component () => {} at compile time?
You could wrap the required components inside a higher order component that checks whether the component is blacklisted or not.
for example :
class YourComponent extends Component {
constructor(props){
super(props);
}
render(){
return(
// your component goes here ..
);
}
}
export default WithPermission(YourComponent);
check if the component needs to be rendered or not inside the HOC WithPermission.
function withPermission(YourComponent) {
class WithPermission extends React.Component {
constructor(props) {
super(props);
}
// you can check the props inside ComponentDidMount and set a flag if
// the component satisfies the criteria for rendering.
render() {
const {blacklistedComponents,...rest} = this.props;
if(!blackListedComponents){
return <YourComponent {...rest} />
}
else{
return null;
}
}
}
}
There is no such functionality out of box.
You may shim React rendering cycle, I mean shim React.createElement method and validate component before it is added to VDOM
All JSX is processed through React.createElement
e.g. at the start of app add
let React = require('react');
let originalCreateElement = React.createElement;
React.createElement = function() {
let componentConstructorOrStringTagName = arguments[0];
if (isBlacklisted(componentConstructorOrStringTagName)) {
return null;
}
return originalCreateElement.apply(this, arguments);
}
The best idea I can think of is to "shim" react and Component
if you are using webpack you can use this:
https://webpack.js.org/guides/shimming/
in the bottom line that means instead of importing react you will import your own class of react.
In your new class you could extend React Component and place a check on the render function or something similar.
You could implement a custom ESLint rule and catch this as soon as a dev tries to use a blacklisted components. The id-blacklist rule is similar to what you want, but at the identifier level. The source code looks simple. Maybe you can adapt it to disallow more then just identifiers.
Consider the following solution:
Let there be a file where you declare which components are blacklisted:
let blacklist = [{
name: 'secretComponent',
invoke: (props)=> {
return <SecretComponent ...props />
},
isBlacklisted: true
},{
name: 'home',
invoke: (props)=> {
return <HomeComponent ...props />
},
isBlacklisted: false
},{
name: 'login',
invoke: (props)=> {
return <LoginComponent ...props />
},
isBlacklisted: false
}];
Define a Higher Order Component like below:
function renderIfNotBlacklisted(name) {
let component = blacklist.map(x=> x.name == name); //blacklist from above
if (!component.isBlacklisted){
return component.invoke();
} //else can be handled as you will
//You can keep a default component to render or send empty values
}
Call this component in the render function wherever you want this feature to work. This way you have a centralized location to managed blacklisted components (blacklist.json can be in the root of react project or fetched from API on first run)

ReactJS: What do first parameter in addEventListener, ReactDOM.render(), and () mean in return statement?

I'm studying ReactJS and came across the following component example:
class MyComponent extends React.Component {
constructor(props) {
super(props);
// set the default internal state
this.state = {
clicks: 0
};
this.clickHandler = this.clickHandler.bind(this);
}
componentDidMount() {
this.refs.myComponentDiv.addEventListener(
‘click’,
this.clickHandler
);
}
componentWillUnmount() {
this.refs.myComponentDiv.removeEventListener(
‘click’,
this.clickHandler
);
}
clickHandler() {
this.setState({
clicks: this.clicks + 1
});
}
render() {
let children = this.props.children;
return (
<div className=”my-component” ref=”myComponentDiv”>
<h2>My Component ({this.state.clicks} clicks})</h2>
<h3>{this.props.headerText}</h3>
{children}
</div>
);
}
}
What is the first parameter, 'click', mean in this.refs.myComponentDiv.removeEventListener() and this.refs.myComponentDiv.removeEventListener()? And why do you have to pass in props to super()? And what does the () mean in ({this.state.clicks} clicks})?
Lastly, I came across a stateless component:
const StatelessCmp = (props) => {
return (
<div className=”my-stateless-component”>
{props.name}: {props.birthday}
</div>
);
};
// ---
ReactDOM.render(
<StatelessCmp name=”Art” birthday=”10/01/1980” />,
document.getElementById(“main”)
);
And when do you choose to use a stateless component? And when do you use and what does ReactDOM.render() do, especially the document.getElementById(“main”) portion? Because typically, you would simply do export default ....
And in the following, will simply the two <p>'s be displayed on top of the <MyComponent/> class?
<MyComponent headerText=”A list of paragraph tags”>
<p>First child.</p>
<p>Any other <span>number</span> of children...</p>
</MyComponent>
Thank you and will be sure to upvote and accept answer!
'click' is the name of the click event which is created when you click in the viewport/element
As you are extending the class React.Component you have to pass the properties of your class to the super class (React.Component) that it is correctly instantiated. For more infos read a book about Object oriented programming
I cannot find the statement ({this.state.clicks} clicks}) in your code.
If you do not use this.state use a stateless component
ReactDOM.render() actually creates and renders your components to your page. The document.findElementById('main') is looking for the html element with id="main" that ReactDOM can render it into this element.
I would recommend that you read a basic book or take a online tutorial in javascript first before you learn a js framework like React

React: componentDidMount + setState not re-rendering the component

I'm fairly new to react and struggle to update a custom component using componentDidMount and setState, which seems to be the recommended way of doing it. Below an example (includes an axios API call to get the data):
import React from 'react';
import {MyComponent} from 'my_component';
import axios from 'axios';
export default class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
data: []
};
}
GetData() {
return axios.get('http://localhost:5000/<route>');
}
componentDidMount() {
this.GetData().then(
(resp) => {
this.setState(
{data: resp.data}
)
}
)
}
render() {
return (
<MyComponent data={this.state.data} />
);
}
}
Doing console.log(this.state.data) just below render() shows that this.state.data does indeed get updated (from [] to whatever the API returns). However, the problem appears to be that MyComponent isn't rendered afresh by componentDidMount. From the Facebook react docs:
Setting state in this method will trigger a re-rendering.
This does not seem to be the case here: The constructor of MyComponent only gets called once (where this.props.data = []) and the component does not get rendered again. I'd be great if someone could explain why this is and whether there's a solution or a different way altogether to get the updating done.
UPDATE
I've added the code for MyComponent (minus some irrelevant features, as indicated by ...). console.log(data_array) prints an empty array.
import React from 'react';
class DataWrapper {
constructor(data) {
this._data = data;
}
getSize() {
return this._data.length;
}
...
}
export class MyComponent extends React.Component {
constructor(props) {
super(props);
this._dataWrapper = new DataWrapper(this.props.data);
this.state = {
data_array: this._dataWrapper,
};
}
render() {
var {data_array} = this.state;
console.log(data_array);
return (
...
);
}
}
You are falling victim to this antipattern.
In MyComponent constructor, which only gets called the first time it mounts, passed your empty array through new DataWrapper and now you have some local state which will never be updated no matter what your parent does.
It's always better to have one source of truth, just one state object anywhere (especially for things like ajax responses), and pass those around via props. In fact this way, you can even write MyComponent as a simple function, instead of a class.
class Example extends Component {
state = { data: [] }
GetData() { .. }
componentDidMount() {
this.GetData().then(res =>
this.setState({data: new DataWrapper(res.data)})
)
}
render() { return <MyComponent data={this.state.data} /> }
}
...
function MyComponent (props) {
// props.data will update when your parent calls setState
// you can also call DataWrapper here if you need MyComponent specific wrapper
return (
<div>..</div>
)
}
In other words what azium is saying, is that you need to turn your receiving component into a controlled one. Meaning, it shouldn't have state at all. Use the props directly.
Yes, even turn it into a functional component. This helps you maintain in your mind that functional components generally don't have state (it's possible to put state in them but ... seperation of concerns).
If you need to edit state from that controlled component, provide the functions through props and define the functions in the "master" component. So the master component simply lends control to the children. They want anything they talk to the parent.
I'm not posting code here since the ammendment you need to make is negligible. Where you have this.state in the controlled component, change to this.props.

Categories

Resources