Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

react context with componentdidupdate

I am running a pattern like so, the assumption is that SearchResultsContainer is mounted and somewhere a searchbar sets the input.

class SearchResults {
   render() {
      return(
        <ResultsContext.Consumer value={input}>
        {input => <SearchResultsContainer input=input}
        </ResultsContext.Consumer>
   )
}

class SearchResultsContainer
  componentDidUpdate() {
      //fetch data based on new input
      if (check if data is the same) {
        this.setState({
          data: fetchedData
        })
      }
  }
}

this will invoke a double fetch whenever a new context value has been called, because componentDidUpdate() will fire and set the data. On a new input from the results context, it will invoke componentDidUpdate(), fetch, set data, then invoke componentDidUpdate(), and fetch, then will check if data is the same and stop the loop.

Is this the right way to be using context?

like image 418
goda Avatar asked Jan 01 '23 14:01

goda


2 Answers

The solution I used is to transfer the context to the props through a High Order Component.

I have used this very usefull github answer https://github.com/facebook/react/issues/12397#issuecomment-374004053

The result looks Like this : my-context.js :

import React from "react";

export const MyContext = React.createContext({ foo: 'bar' });

export const withMyContext = Element => {
  return React.forwardRef((props, ref) => {
    return (
      <MyContext.Consumer>
        {context => <Element myContext={context} {...props} ref={ref} />}
      </MyContext.Consumer>
    );
  });
};

An other component that consumes the context :

import { withMyContext } from "./path/to/my-context";

class MyComponent extends Component {
 componentDidUpdate(prevProps) {
    const {myContext} = this.props
    if(myContext.foo !== prevProps.myContext.foo){
      this.doSomething()
    }
  }
}
export default withMyContext(MyComponent);

There must be a context producer somewhere :

<MyContext.Provider value={{ foo: this.state.foo }}>
  <MyComponent />
</MyContext.Provider>

like image 141
Stephane L Avatar answered Jan 08 '23 19:01

Stephane L


Here is a way to do it that doesn't require passing the context through props from a parent.

// Context.js

import { createContext } from 'react'

export const Context = createContext({ example: 'context data' })

// This helps keep track of the previous context state
export class OldContext {
    constructor(context) {
        this.currentContext = context
        this.value = {...context}
    }
    update() {
        this.value = {...this.currentContext}
    }
    isOutdated() {
        return JSON.stringify(this.value) !== JSON.stringify(this.currentContext)
    }
}
// ContextProvider.js

import React, { Component } from 'react'
import { Context } from './Context.js'
import { MyComponent } from './MyComponent.js'

export class ContextProvider extends Component {
    render(){
        return (
            <MyContext.provider>
                {/* No need to pass context into props */}
                <MyComponent />
            </MyContext.provider>
        )
    }
}
// MyComponent.js

import React, { Component } from 'react'
import { Context, OldContext } from './Context.js'

export class MyComponent extends Component {
    static contextType = Context

    componentDidMount() {
        this.oldContext = new OldContext(this.context)
    }

    componentDidUpdate() {
        // Do all checks before updating the oldContext value
        if (this.context.example !== this.oldContext.value.example) {
            console.log('"example" in context has changed!')
        }

        // Update the oldContext value if the context values have changed
        if (this.oldContext.isOutdated()) {
            this.oldContext.update()
        }
    }

    render(){
        return <p>{this.props.context.example}</p>
    }
}
like image 28
Daniel Tonon Avatar answered Jan 08 '23 20:01

Daniel Tonon