Higher Order Components - React.JS - reactjs

Trying to pass initial value 10 to the Higher Order Components.
import HOC from './HighOrderComponent'
class ClickHandler extends Component{
state= {val : 10}
increment = () => {
this.props.increment(this.state.val)
}
render(){
return <button onMouseOver={this.increment}>Mouse Over {this.props.value}</button>
}
}
export default HOC(ClickHandler)
Here, I do not know how to use the value to update the state of
const HOC = (OriginalComponent) => {
class HigherOrderComponent extends Component{
state = { count : 0 }
increment = (value) => {
this.setState(prev => ( {count : prev.count + 1 }))
}
render(){
return <OriginalComponent value={this.state.count} increment={this.increment}/>
}}
return HigherOrderComponent
}
export default HOC
Expected Result: On load, Mover Over 10, once hovered, it will increment to 11, 12, 13.....
thanks.

The issue with your components is that HOC component's state overrode your 10 state defined in your ClickHandler.
If you changed state = { count : 0 } to state = { count : 10 } you will get the expected initial value of 10.
But seeing the code, what is the purpose of having state value maintained in both components? And the state value of ClickHandler is not even used by the HOC.
Guessing you want to maintain the state within HOC and simply wanted to pass in an initial value for it.
Would suggest the following changes:
class ClickHandler extends Component{
// no need to maintain state again in `ClickHandler`
// simply use the increment props for onMouseOver
render(){
return <button onMouseOver={this.props.increment}>Mouse Over {this.props.value}</button>
}
}
// add extra param for HOC to pass in initialValue of 10
export default HOC(ClickHandler, 10);
const HOC = (OriginalComponent, initialValue) => {
class HigherOrderComponent extends Component{
state = { count : initialValue }
// no need to get the state value from `ClickHandler`
increment = () => {
this.setState(prev => ( {count : prev.count + 1 }))
}
render(){
return <OriginalComponent value={this.state.count} increment={this.increment}/>
}}
return HigherOrderComponent
}

Related

Called componentDidMount twice

I have a small react app. In App.js I have layout Sidenav and Content area. The side nav is shown on some page and hid from others. When I go to some components with sidenav, sidenav flag is set by redux and render the component again, in the componentDidMount I have api call, and it is executed twice.
class App extends Component {
renderSidebar = () => {
const {showNav} = this.props;
return showNav ? (
<TwoColumns>
<Sidenav/>
</TwoColumns>) : null;
};
render() {
const {showNav} = this.props;
const Column = showNav ? TenColumns : FullColumn;
return (
<Row spacing={0}>
{this.renderSidebar()}
<Column>
<Route exact path="/measurements/:id/:token/:locale/measure"
component={MeasurementPage}/>
</Column>
</Row>
)
}
}
const mapStateToProps = state => ({
showNav: state.sidenav.showNav
});
export default connect(mapStateToProps)(App);
I tried to use shouldComponentUpdate to prevent the second API call
class MeasurementPage extends Component {
constructor(props) {
// This update the redux "showNav" flag and re-render the component
props.toggleSidenav(false);
}
shouldComponentUpdate(nextProps) {
return !nextProps.showNav === this.props.showNav;
}
componentDidMount() {
// This is executed twice and made 2 api calls
this.props.getMeasurement(params);
}
render() {
return <h1>Some content here</h1>;
}
}
const mapStateToProps = state => ({
showNav: state.sidenav.showNav
});
export default connect(mapStateToProps)(MeasurementPage);
Did someone struggle from this state update and how manage to solve it?
This props.toggleSidenav(false) might cause side effect to your component lifecycle. We use to do this kind of stuff inside componentWillMount and it has been depreciated/removed for a reason :). I will suggest you move it inside componentDidMount
class MeasurementPage extends Component {
constructor(props) {
// This update the redux "showNav" flag and re-render the component
// props.toggleSidenav(false); // remove this
}
shouldComponentUpdate(nextProps) {
return nextProps.showNav !== this.props.showNav;
}
componentDidMount() {
if(this.props.showNav){ //the if check might not necessary
this.props.toggleSidenav(false);
this.props.getMeasurement(params);
}
}
render() {
return <h1>Some content here</h1>;
}
}
The comparison should be
shouldComponentUpdate(nextProps) {
return !(nextProps.showNav === this.props.showNav)
}
The problem is that !nextProps.showNav negate showNav value instead of negating the role expression value, and that is why you need an isolation operator.
It's No call twice anymore.
componentDidMount() {
if (this.first) return; this.first = true;
this.props.getMeasurement(params);
}

Changing the state of parent component inside a child

I have started learning react and I couldn't find the answer for one of my questions.
Let's say I have a parent component called main where I have states and one of them is a counter.
Inside main i have a child component called secondary with a few buttons.
Is that possible to change state of the parent component using the function inside the child component?
If not, would Redux help to solve that task?
Thanks in advance for clarification
Your parent component can keep a state, initialized in the constructor like follows:
class Main extends Component {
constructor (props) {
super(props)
this.state = { counter: 0 }
}
...
Then, you want to create a method in Main that increments your state counter:
incrementCounter = () => {
this.setState({ counter: this.state.counter + 1 })
}
You can pass this function reference to a child component as a prop. So, you might see this in the render method of Main:
<Child incrementParent={this.incrementCounter} />
Then, in the Child, whenever you'd like, you can call this.props.incrementParent() - and this will call the function that was passed to it from Main, which will increment the state counter for Main.
Here is a code sandbox that shows all of this put together. In it, there is a parent container (Main) with its own state counter. There is a Child component which has 3 buttons. Each button click increments its own counter, but also any button click will increment the parent's counter.
Hope this helps!
Yes, you can create a function that updates the parent state, and pass it down via props, context or redux if your particular app needs it.
E.g. Using React hooks
function Parent() {
const [value, setValue] = useState(1);
const increment = () => {
setValue(value + 1);
}
return (
<Child increment={increment} />
);
}
function Child(props) {
const { increment } = props;
return (
<button onClick={increment} />
);
}
class Parent extends Component {
state = { counter: 0 };
updateCounter = () => {
this.setState(prevState => ({
counter: prevState.counter + 1
}))
};
render() {
return (
<>
<Text>{this.state.counter}</Text>
<ChildComponent updateCounter={this.updateCounter} />
</>
);
}
}
class ChildComponent extends Component {
...
render() {
return (<Button onPress={this.props.updateCounter}><Text>{'Press Me!'}</Text></Button>)
}
}
You should pass the function of updating your counter updateCounter from parent, pass that reference to ChildComponent
You can update parent state by passing a function to the child component to update the parent.
Upon calling updateCounterfunction in the example, you can decrement or increment the value of count by supplying an operator in the function's argument which can be plus or minus
class Parent extends Component {
state = {
count: 1
};
updateCounter = operator => {
if (["plus", "minus"].includes(operator)) {
this.state(prevState => ({
count: prevState.count + (operator === "plus" ? 1 : operator === "minus" ? -1 : 0)
}));
}
}
render() {
const { count } = this.state;
return (
<Child count={count} updateCounter={this.updateCounter} />
);
};
};
const Child = props => (
<div>
<button onClick={() => props.updateCounter("minus")}>MINUS</button>
<button onClick={() => props.updateCounter("plus")}>PLUS</button>
<h1>{props.count}</h1>
</div>
);

How to set defaultProp value based on value of other prop?

I have a component where the default value of one prop depends on the value of another prop (default or user provided). We can't do the following because we don't have access to this:
static defaultProps = {
delay: this.props.trigger === 'hover' ? 100 : 0,
trigger: 'hover'
};
How can I best do this?
I'd rather suggest you to:
store that variable as an instance variable in the component's class
evaluate if it is a state variable rather than a prop (or instance)
By the way in both cases you should check when new props arrive to the component and update it if needed.
I'd go for the state variable and write something like this:
class MyComponent extends React.Component {
static propTypes = {
trigger: PropTypes.string,
}
static defaultProps = {
trigger: 'hover',
}
constructor(props) {
super(props);
this.state = {
delay: this.computeDelay(),
}
}
componentWillReceiveProps(nextProps) {
const { trigger: oldTrigger } = this.props;
const { trigger } = nextProps;
if (trigger !== oldTrigger) {
this.setState({
delay: this.computeDelay(),
})
}
}
computeDelay() {
const { trigger } = this.props;
return trigger === 'hover' ? 100 : 0;
}
render() {
...
}
}
In this way you can use this.state.delay in the render method without taking care of determining its value.
You can do it inside the render method.
render() {
const delay = this.props.trigger === 'hover' ? 100 : 0;
// Your other props
return (
<SomeComponent delay={delay} />
// or
<div>
{/*...something else, use your delay */}
</div>
);
}
Using a function component you can do it like this:
function MyComponent({
trigger,
delay: trigger === 'hover' ? 100 : 0,
}) {
return <div>...</div>
}
MyComponent.propTypes = {
trigger: PropTypes.string.isRequired,
delay: PropTypes.number.isRequired,
};
I was facing a similar issue and I found a method based solution missing in this discussion therefore i am writing this answer
There are two cases when you might want to pass default props
Case 1: when you want to choose defaultProps based on a Static value
Case 2: when you want to choose defaultProps based on a Method
Solution for Case 1
class Shape extends Component{
static defaultProps = {
colour: 'red',
}
render(){
const {colour} = this.props;
// Colour will always be 'red' if the parent does not pass it as a prop
return <p>{colour}</p>;
}
}
solution for Case 2
class Shape extends Component{
calcArea = () => {
console.log("Area is x");
}
render(){
const {calcArea} = this.props;
// calcArea will be evaluated from props then from the class method
return <button onClick={calcArea || this.caclArea}></button>;
}
}

React State not available from Parent?

I have a form with a child component that renders as a table.
ParentComponent extends React {
state = {
anArray: []
}
<ParentComponent>
<table>
map ( thing => <ChildComponent {someFunction= this.updateFunction;} />
When ChildComponent maps the data to individual TD's. In my onChange in the ChildComponent, I'm invoking
onChange = this.props.someFunction();
and the code is hitting my breakpoint which is great. It calls someFunction in the ParentComponent. In someFunction, I'm trying to access the parent's state so I can match the onChanged TD with the proper index in the array but I'm getting undefined.
someFunction(id) {
const index = this.state.anArray.findIndex( x => x.id === id) ;
if (index === -1)
// handle error
console.log("DIDN'T FIND ID: " + id);
});
}
Why wouldn't I have access to state on the function invocation from the ChildComponent? I expected to be able to access it.
It's not clear from the posted code, but I guess you haven't bind the someFunction and you have the context of the child, instead of parent's.
ParentComponent extends React {
constructor(props){
super(props)
this.someFunction = this.someFunction.bind(this)
}
someFunction(){
...
}
render(){
...
}
}
If you have the necessary babel plugins you can even do
ParentComponent extends React {
someFunction = () => {
...
}
render(){
...
}
}

Children ref undefined react native

I created a component wrapper around ViewPagerAndroid (simplified version)
class TabView extends Component {
constructor(props){
super(props)
this.state = { position: 0 }
}
changePage = (key) => {
this._pagerRef.setPage(key)
this.setState({position: key})
}
render(){
return(
<ViewPagerAndroid ref={(ref) => this._pagerRef = ref}>
{ this.props.scenes }
</ViewPagerAndroid>
)
}
}
I want to trigger changePage from outside the component (eg from: <TabView ref={(ref) => this._ref = ref} />, and run this._ref.changePage(key)).
However, each time I try to do so, this._pagerRef is undefined inside the changePage function of TabView.
What am I missing ?
There is a more idiomatic React solution to the problem you are trying to solve -- namely making TabView a controlled component and setting ViewPager page on componentDidUpdate:
class TabView extends Component {
componentDidUpdate = ({ page }) => {
// call setPage if page has changed
if (page !== this.props.page && this._pagerRef) {
this._pagerRef.setPage(page);
}
};
render() {
return (
<ViewPagerAndroid
initialPage={this.props.page}
ref={ref => this._pagerRef = ref}
onPageSelected={e => this.props.pageChanged(e.nativeEvent.position)}
>
{this.props.scenes}
</ViewPagerAndroid>
);
}
}
You can then move the current page tracking to the parent component's state and pass it down to TabView as a prop, along with a handler that updates it when the value changes:
render() {
return (
<TabView
page={this.state.page}
pageChanged={page => this.setState({page})}
/>
)
}
You're trying to access the ref from outside of the component which has no instance to it.
Therefore you need to pass it as a prop from the parent component itself. Also you need to move the changePage to the parent component to access it from outside.
Parent
changePage = (key) => { //... Call the function here
this._pagerRef.setPage(key)
this.setState({position: key})
}
accessRef (ref) {
this._pagerRef = ref . //... Bind the ref here
}
<TabView passRef={this.accessRef} /> //...Pass the ref here
Child
<ViewPagerAndroid ref={this.props.passRef}> . // ... Set the ref here
{ this.props.scenes }
</ViewPagerAndroid>

Resources