loading...
Cover image for React: quicks

React: quicks

codermonkey profile image Shihabudheen US Updated on ・5 min read

setState updater form

  • use when your state update depends on the current value of state
  • it helps you name your updater
  • helps react to easily find the proper instance
  • when we use the object form of setState, the this.props and this.state values that you use may be unreliable as they are updated asynchronously
this.setState(state=>{
  return { 
    count: state.count + 1
  }
})

Error Boundaries

  • with React 15, when the app ran into an uncaught error, the app gets stuck in the current state. With that, the user will find it difficult to understand what went wrong
  • with React 16, this behaviour was changed and now the component which threw the error gets unmounted. In dev mode, we can get detailed info about the component which threw the error
  • by now, React started enforcing the use if ErrorBoundaries which are basic components with componentDidCatch and getDerivedStateFromError lifecycles implemented.
  • with that, we can show the user a proper error fallback and she/he can have a better idea of what is happening. We can handle the retry cases too.
class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    // Update state so the next render will show the fallback UI.
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    // You can also log the error to an error reporting service
    logErrorToMyService(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      // You can render any custom fallback UI
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children; 
  }
}
  • use ErrorBoundary when you work with an API or something which can easily throw unhandled errors.

getDerivedStateFromProps(props,state)

  • a replacement for componentWillReceiveProps(nextProps)
  • it is a static method on the class
  • called in the initial render, unlike the componentWillReceiveProps which called only on update
state={
  direction:"initial"
}

componentWillReceiveProps(nextProps){
  if(this.props.value>nextProps.value){
    this.setState({direction:"down"})
  }else if(this.props.value<nextProps.value){
    this.setState({direction:"up"})
  }
}

...
state={
 direction:"initial"
}

static getDerivedStateFromProps(props,state){
  if(!state.value){
    return {
      value:props.value // set on initial render
    }
  } else if(state.value < props.value){
    return {
      value: props.value,
      direction:"up"
    }
  } else if(state.value > props.value){
    return {
      value: props.value,
      direction:"down"
    }
  }
}

  • since we don't have access to this instance inside getDerivedStateFromProps as it is static, we store the previous prop value inside the state to compare it with the incoming props

getSnapshotBeforeUpdate(prevProps, prevState)

  • invoked just before the changes are committed to the DOM
  • replacement for componentWillUpdate(nextProps, nextState)
  • best place to read DOM values
  • it return a value, which can be access as the third argument inside componentDidUpdate(). Usually, it is called snapShot
componentWillUpdate(){
  const {scrollHeight, scrollTop, clientHeight } = document.documentElement
  this.scrolledUp = clientHeight+scrollTop < scrollHeight
}

...

getSnapshotBeforeUpdate(){
  const {scrollHeight, scrollTop, clientHeight } = document.documentElement
  return clientHeight+scrollTop < scrollHeight
}

componentDidUpdate(prevProps, prevState, snapshot:wasScrolledUp){
  ...
  if(!wasScrolledUp){
    ... 
  }
}

componentWillMount()

  • componentWillMount() is the place where we did state initialisation and subscriptions
  • but with async mode, there are chances where a component may not get mounted and to avoid memory leaks React is deprecating componentWillMount
  • the use cases of componentWillMount can still be fulfilled by existing lifecycle methods
componentWillMount(){
  this.setState({
    messages:getInitialMessage()
  })
  subscribeToMessages(message=>{
    this.setState((state)=>{ 
       messages: state.messages.concat(message)
    })
  })
}

...

state={
 messages:getInitialMessage()
}

componentDidMount(){
  subscribeToMessages(message=>{
    this.setState((state)=>{ 
       messages: state.messages.concat(message)
    })
  })
}
  • if you still think you might miss out some important info because of a delayed subscription you can use create-subscription

data fetching in componentWillReceiveProps()

  • most of the time we use componentWillReceiveProps() to fetch some data as some of our props changes
componentDidMount(){
 this.fetch(this.props.id)
}

async fetch(id){
  const data= await fetch(`....../${id}`)
}

componentWillReceiveProps(nextProps){
  if(this.props.id!==nextProps.id){
    this.setState({loading:true})
    this.fetch(nextProps.id)
  }
}

...

componentDidMount(){
 this.fetch()
}

async fetch(){
  const data= await fetch(`....../${this.props.id}`) // everywhere it is `this.props` πŸ™‚
}

componentDidUpdate(prevProps){
  if(prevProps.props.id!==this.props.id){
    this.setState({loading:true}) // this causes an extra render 😒
    this.fetch()
  }
}
  • to avoid all this hustle we have a simple workaround. Suppose the above code belongs to a component called <Card id={person.id}/>. In that case, we can just pass the person.id as the key to the component and React will handle the rest for you. When the key changes React will unmount and mount a new component for you. So we can basically shorten the code as
componentDidMount(){
 this.fetch()
}

async fetch(){
  const data= await fetch(`....../${this.props.id}`) // everywhere it is `this.props` πŸ™‚
}

no need of fetch call inside componentDidUpdate at all

refs

In non-react world, we have different ways to reference or identify DOM element. We usually use querySelectors,document.getElementById,etc. But in react, it is not encouraged much, because we can have multiple instances of the same component and if we use an id to identify a DOM element, it would cause problems.

So in React worl we have refs. refs are usually used to "ref"erence DOM element in React.

string refs (no more favoured)

<input ref="input" ..../>

Usage: this.refs.input

  • this was a bit problematic since React has to maintain a global state and there was a bit of indirection involved in this method

callback ref

<input ref={node=>(this.input = node)} ..../>

Usage: this.input

  • now it is more straightforward

createRef

input=createRef()

<input ref=this.input ..../>

Usage: this.input.current

forwardRef

  • suppose if you want to pass down the ref from parent to child, it will not work as a normal prop
  • by default, React doesn’t password the ref
...
<FancyInput ref={node => (this.counter=node)}/>

function FancyCounter({ref,...props}){
  return <input ref={ref} {...props}/> // this will not work 😒
}
  • before forwardRef what we used to do was, give the ref a different name and access it in child
...
<FancyInput innerRef={node => (this.counter=node)}/>

function FancyCounter({innerRef,...props}){
  return <input ref={innerRef} {...props}/> // this will not work 😒
}
  • but now React has forwardRef
...
<FancyInput innerRef={node => (this.counter=node)}/>

function FancyCounter=React.forwardRef((props,ref)=> {
  return <input ref={ref} {...props}/> // this will not work 😒
})

createContext

  • avoids prop drilling
class Parent extends React.Component{
 static childContextTypes = {
   value:PropTypes.Object.isRequired
 }

 getChildContext(){
   return { name: ... }
 }

 render(){
   return this.props.children
 }
}

class App extends React.Component{
  ...
  render(){ 
    <Parent value={...}>
       <Child/> 
    </Parent>
  }
}

class Child extends React.Component{
  static contextTypes={ 
     value:PropTypes.Object.isRequired
  }

  render(){
    <>
      <p>{value.name}</p>
    </>
  }
}
  • breaks if the consumer is a PureComponent because even though the context value changes, the PureComponent can't detect the change
  • thus came the createContext API
const ParentContext=React.createContext(_defaultValue_)

class App extends React.Component{
  ...
  render(){ 
    <ParentContext.Provider value={...}>
       <Child/> 
    </ParentContext.Provider>
  }
}

class Child extends React.Component{
  render(){
    <ParentContext.Consumer>
      {value=>(<p>{value.name}</p>)}
    </ParentContext.Consumer>
  }
}

React Async Mode

Posted on by:

codermonkey profile

Shihabudheen US

@codermonkey

Have a dream big enough so that you can't sleep at all.

Discussion

markdown guide