Modern JavaScript is self documenting. So remember to write the code for humans. Keep it simple. I suggest also keeping structural conventions organised around the project. JavaScript version ES6, React 16 and Babel (also new version 7) can help us a bit with these.
The React API is simple, but, from time to time you should return to the official React docs that are updated with new features and with validated ways of using them. Like this nice example of Async Refs: https://reactjs.org/docs/refs-and-the-dom.html#callback-refs
But if you have really hit the stage where your code becomes more of a legacy and not ready to be updated to a newer React version, my suggestion for you is to head to Frontend Masters. It has modern but easily approachable video courses, all available by a monthly subscription. You really should check them out: https://frontendmasters.com/
UI with components
Create components in a declarative manner so that the person using it doesn’t need to think about how it does everything it implements. With the React API itself, you can declaratively compose the UI. Just aim for a developer-friendly component interface that allows developers to compose your components easily. Remember that good abstractions make it easy to add code and behaviour, but really good abstractions make it easy to remove code and behaviour.
Learn destructuring
One of the best places to get detailed information about JavaScript, ES6 and browser support (without babel or polyfilling) is Mozilla developer Web docs. So learn the basics of destructuring from here https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment
Instead of:
(Props) => {…} |
Destructure:
({ disabled, title = 'Default title' , name: renamedNameProp, component: RenamedComponent = DefaultComponent, header: {title: headerTitle}, menu: {menuClosed, selectedItem: {name: menuSelectedItemdName}}, arr: [arrFirstValue, arrSecondValue, arrThirdValue], [keyNameAsStringVariable]: keyNewName, } = {}) => {…} |
Or:
const {disabled, title = 'Default title' , etc...} = props; //Or this.props |
Going through object keys and values
The other important things that I take as basic knowledge for modern JavaScript are ES6 array methods. If you are not familiar with them, you might want to start from here https://gofore.com/en/must-learn-javascript-array-methods-that-will-help-you-become-a-better-coder/
Although Array.map is one of the most used ones – and for valid reasons – I still prefer the more powerful Array.reduce. (And map is just one implementation of reduce).
Example: Go through an object (key-value pairs) and check condition for each key to either return a new modified value or the original value.
Object.entries(state) .reduce((newState, [key, value]) => ( { ...newState, [key]: checkCondition(key) ? modifyValue(value) : value } ), {}) |
Array functions can also be used for just to clear your code:
return [changes] .map(c => typeof c === 'function' ? c(currentState) : c) .map(c => this .props.stateReducer(currentState, c) || {}) .map(c => Object.keys(c).length ? c : null )[ 0 ]; |
Public class fields
Babel will transpile your code and add a constructor for you behind the scenes. As described here https://hackernoon.com/the-constructor-is-dead-long-live-the-constructor-c10871bea599
So instead of this:
class Button extends Component { constructor(props) { super (props); this .state = { type: buttonType. default }; this .handleClick = this .handleClick.bind( this ); } handleClick() {...} render() { return (...); } } |
Write this:
class Button extends Component { state = { type: buttonType. default } handleClick = () => {...} render() { return (...); } } |
State changes with higher order functions
Note: State updates may be asynchronous by batching multiple setState() calls into a single update for performance: https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous
If you know how to use higher order functions, here’s a really neat example to get you going also with React:
const makeUpdater = apply => key => state => ({[key]: apply(state[key])}); const toggleKey = makeUpdater(previous => !previous); const incrementKey = makeUpdater(previous => previous + 1 ); this .setState(toggleKey( 'openMenu' )); this .setState(incrementKey( 'clickCounter' )); // Or the same without makeUpdater const toggleKey = key => state => ({[key] : !state[key]}); const incrementKey = key => state => ({[key] : state[key] + 1 }); |
If this is is something totally new for you I can recommend these video tutorials by Fun Fun Function. Here are a few to get you started:
Higher-order functions: https://www.youtube.com/watch?v=BMUiFMZr7vk
Currying: https://www.youtube.com/watch?v=iZLP4qOwY8I
Deprecated componentWillReceiveProps!
ComponentWillReceiveProps has been deprecated and you should update their usage to componentDidUpdate. It will stop working at React 17. Read more about this and other (componentWillMount, componentWillUpdate) now legacy lifecycle methods from official React docs: https://reactjs.org/docs/react-component.html#legacy-lifecycle-methods
Example for refactoring:
componentWillReceiveProps(nextProps) { if (nextProps.id !== this .props.id) { this .fetch(nextProps); } } // replace with: componentDidUpdate(prevProps) { if ( this .props.id !== prevProps.id) { this .fetch(); } } |
Inspect the DOM right before render updates it (React 16.3)
Read the current DOM state right before the changes from VDOM are to be reflected in the DOM. It can be done using getSnapshotBeforeUpdate lifecycle method.
Example: Before the component gets a new render, detect if the user hasn’t scrolled the scrollbar all the way to the bottom.
getSnapshotBeforeUpdate() { const { clientHeight, scrollTop, scrollHeight } = document.documentElement; return { scrollNotBottom: clientHeight + scrollTop < scrollHeight } } componentDidUpdate(prevProps, prevState, snapshot) { const { scrollNotBottom } = snapshot; } |
Fragment or array (React 16.2)
Keep semantics clear. Don’t add unnecessary elements that just increase complexity (and complicate your CSS). Instead of adding extra wrappers (like <div>’s) now you can also return a Fragment or an array.
import React, { Component, Fragment } from 'React' ; return ( <Fragment> { this .props.title} { this .state.count} <p>{descriptions.map((desc, i) => <Fragment key={i}>{i > 0 && ', ' }{desc}</Fragment> )}</p> </Fragment> ); return ( <> { this .props.title} { this .state.count} <p>{descriptions.map((desc, i) => <Fragment key={i}>{i > 0 && ', ' }{desc}</Fragment> )}</p> </> ); return [ { this .props.title }, { this .state.count }, <p>{descriptions.map(desc => i > 0 ? `, ${desc}` : desc)}</p> ]; |
ErrorBoundary (React 16.0)
Create ErrorBoundary component and log your errors. Introduced at the React official docs: https://reactjs.org/docs/error-boundaries.html
const logError = (error, info) => ... class ErrorBoundary extends React.Component { state = { hasError: false } componentDidCatch(error, info) { logError(error, info) this .setState({ hasError: true }); } render() { return this .state.hasError ? this .props.fallbackComponent : this .props.children; } } <ErrorBoundary fallbackComponent={IHasErrorHere}> <HereMyComponent /> </ErrorBoundary> |
Compound components, render prop, higher order components
A modern React developer should also learn how to make compound components, render prop, higher order components and other fancy patterns. Here’s a really good post briefly explaining them: https://blog.kentcdodds.com/mixing-component-patterns-4328f1e26bb5
Example: Figure out how much the document is scrolled vertically and pass it to a CSS custom property.
class ScrollY extends Component { state = { y: 0 } handleWindowScroll = () => { this .setState({ y: Math.round(window.scrollY) }); } componentDidMount() { this .handleWindowScroll(); window.addEventListener( 'scroll' , this .handleWindowScroll); } componentWillUnmount() { window.removeEventListener( 'scroll' , this .handleWindowScroll); } render() { return this .props.render( this .state.y); } } // Usage <div className= "app" > <ScrollY render={y => <div style={{ '--scroll-y' : y }}> <p>Here some long scrollable content</p> </div> } /> </div> |
Experimental bonus: optional chaining (with Babel 7)
Babel 7 has a new plugin called babel-plugin-transform-optional-chaining that is based on ECMAScript TC39 proposal. Now instead of checking for null or undefined of every nested object separately, you can just chain them like this:
const baz = obj?.foo?.bar?.baz; const fromArray = state?.team?.users?.[ 0 ]; const func = state?.user?.getAge?.(); |