Today I Learned

A Hashrocket project

86 posts about #react

Run side effect when a prop changes w/Hooks

There is a React Hook for side effects useEffect. You can pass useEffect a function and that function will run after each render.

useEffect(() => console.log('rendered!'));

In many cases it’s inefficient and unnecessary to call the effect function after every render. useEffect has a second argument of an array of values. If passing in this second argument, the effect function will only run when the values change.

useEffect(() => console.log('value changed!'), [props.isOpen]);

Now, you will see “value changed!” both on the first render and everytime isOpen changes.

Reminder: React Hooks are for functional components not class components. Check out the hooks api here

Get a ref to a dom element with react hooks

React Hooks are now available in React 16.8. There are 10 different hooks, you can read about them here. When I needed a ref to a dom element yesterday I reached for useRef.

const containerRef = useRef(null);

This isn’t a ref to anything unless you pass the ref to a tag.

return (<div ref={containerRef}></div>);

Now the ref will be assigned a dom element that you can use. In this example I’m using the useEffect hook to execute a side effect after the render takes place. Use the current attribute to access the current dom node.

useEffect(() => {
    containerRef.current.style = 'background-color: green;'
})

Define Your Refs with React ElementRef Flow Type

If you’re using flow in your React project, chances are that you are probably using refs. In this case, you’ll need to define your ref types.

Let’s say we have ref on a FlatList component from React Native:

import React from 'react'
import { FlatList } from 'react-native'
import data from './data.json'

class MyList extends React.Component {
  renderItem = () ={
    // rendering stuff
  }
  render() {
  return(
    <FlatList
      ref={l => (this.l = l)}
      data={data}
      renderItem={this.renderItem}
    />)
  }
}

Instead of being lazy and just using any, we’ll use the React.ElementRef flow type. It takes an additional typeof argument, which is the component type of your ref.

// @flow
import * as React from 'react'
import { FlatList } from 'react-native'
import data from './data.json'

class MyList extends React.Component {
  l: React.ElementRef<FlatList>
  renderItem = () ={
    // rendering stuff
  }
  render() {
  return(
    <FlatList
      ref={l => (this.l = l)}
      data={data}
      renderItem={this.renderItem}
    />)
  }
}

Flow docs - ref functions

Navigate With State Via @reach/router

With @reach/router, you can programmatically change your route using the navigate function. This utilizes the Context API, so its available anywhere nested under your router. To provide some data to the destination location, include a state option in the navigate call.

const onSubmit = ({ data }) => {
  /* submit logic ... */

  navigate(nextPath, { state: { data }});
}

The component that renders in response to this navigation will have access to this state.

const NextComponent = ({ location }) => {
  const { data } = location.state;

  return (
    /* ... */
  )
}

Prevent reach/router Redirect Error Screen In Dev

When using @reach/router’s <Redirect> with tools like create-react-app and Gatsby, you’ll get those tools’ development-mode error screen overlays whenever a redirect happens. This has to do with how @reach/router utilizes componentDidCatch to change the path without a render. That error screen overlay can get annoying though. Prevent it with the noThrow prop.

return (
  <Redirect to={anotherPath} noThrow />
);

React Fragments

Ordinarily React requires that each component returns a single child component. Sibling components cannot be returned in the render function:

// Valid
const MyComponent = () => (
  <div>
    <span>Chicken</span>
    <span>and</span>
    <span>Farm</span>
  </div>
);

// Invalid
const MyComponent = () => (
  <span>Chicken</span>
  <span>and</span>
  <span>Farm</span>
);

Sometimes, however, you have no need for the wrapper component and want to just render the siblings. React allows you to do this with the Fragment component, which satisfies the single child requirement without actually rendering a div in the DOM:

import {Fragment} from "react";

// Valid
const MyComponent = () => (
  <Fragment>
    <span>Chicken</span>
    <span>and</span>
    <span>Farm</span>
  </Fragment>
);

// Renders =>
<span>Chicken</span>
<span>and</span>
<span>Farm</span>

Read more about Fragments here: https://reactjs.org/docs/fragments.html

Check The Type Of A Child Component

There is a simple way to check the type of a child component.

import React from 'react';

const Hello = () => <h1>Hello</h1>;

const Greeting = ({ children }) => {
  let hello;
  React.Children.forEach(children, child => {
    if(child.type === Hello) {
      hello = child;
    }
  });

  return hello;
};

This is a comparison of the child’s type to the component constant we are looking for.

This comparison is not the most robust. For instance, Gatsby does something internally that throws off this comparison. Here is a more robust comparison.

if(child.type === Hello || child.type === <Hello />.type)

source

Wrap The Root Of A Gatsby App In A Component

Each component that is defined in the pages directory of a Gatsby app will be generated into a separate static page. Each of these pages is meant to stand on its own. Nevertheless, there is still a behind-the-scenes root component above all of these pages. There are cases where’d you like to wrap this root component with some other component, such as a Redux Provider.

This can be done using the wrapRootElement hook from the Browser API in the gatsby-browser.js file.

// gatsby-browser.js
import React from 'react';
import { Provider } from 'react-redux';

import store from './src/store';

export const wrapRootElement = ({ element }) => {
  return (
    <Provider store={store}>{element}</Provider>
  );
}

Each page and each component in your Gatsby app will now be downstream from a Redux provider meaning that they can connect to the Redux store as needed. You can use this technique for any top-level component that need to be wrapped around the entire app.

source

Gatsby 404 Static Sitemap

Iterating through a process of building many static Gatsby.js pages at once, I discovered a hacky way to see what pages have been built. Visit a page that doesn’t exist, and Gatsy’s development 404 page will provide a sitemap with links to all the static pages.

demo

It’s a little nicer than digging through the filesystem.

@reach/router Renders To A Div

Check out the following snippet that uses @reach/router.

import { Router } from '@reach/router';

const Home = () => <h1>Home</h1>;

const App = () => {
  return (
    <div className="main">
      <Router>
        <Home path="/home" />
      </Router>
    </div>
  );
}

When you visit ‘/home’, this will render in the DOM as:

<div class="main">
  <div tabindex="-1" role="group" style="outline: none;">
    <h1>Home<h1>
  </div>
</div>

Notice the extra div — that is what <Router> renders to as part of @reach/router’s accessibility features. This may throw off the structure or styling of your app. This can be fixed. Any props that you give to <Router> will be passed down to that div. For instance, you could remove the most outer div and put className="main" on the <Router>.

source

Accessing Location Within @reach/router

The API of @reach/router departs a bit from react-router in a couple ways. The location prop which you may be used to having access to automatically is instead available through the Location component.

import React from 'react';
import { Location } from '@reach/router';

const MyComponent = () => {
  return (
    <Location>
      {({ location }) => {
        return <p>Current Location: {location.pathname}</p>;
      }}
    </Location>
  );
}

This is a contrived example, but you can imagine how you’d use it to access state or even create an HOC similar to withRouter.

Formik's Validation Schema As A Function

The most straightforward way to use Formik’s validationSchema is to provide it with a Yup object defining your form’s validations.

const MyComponent = withFormik({
  // ...

  validationSchema: yup.object().shape({
    email: yup.string().required(),
    feedback: yup.string().required(),
  }),
  
  // ...
})(MyForm);

There may be a point at which you need access to the props being passed to MyComponent in order to construct the proper set of validations. Formik supports this by allowing validationSchema to be a function.

const MyComponent = withFormik({
  // ...

  validationSchema: (props) => {
    let emailSchema;
    if(props.allowAnonymous) {
      emailSchema = yup.string();
    } else {
      emailSchema = yup.string().required();
    }

    return yup.object().shape({
      email: emailSchema,
      feedback: yup.string().required(),
    });
  },
  
  // ...
})(MyForm);

When validationSchema is a function, its first argument is the set of props passed to that component.

Don't rerender if nothing changed in React 16.6.0!

React 16.6.0 came out today and React now provides a handy function to create a component that won’t rerender if it doesn’t get new props, React.memo.


const BlueComponent = () => {
  return <div>no props don't rerender</div>;
}

const MemoComponent = React.memo(BlueComponent);

BlueComponent is a component that will re-render every time it’s parent re-renders. It doesn’t take props though, so it won’t look any different based on new props. MemoComponent is a component created by passing BlueComponent to React.memo. It will not re-render when it’s parent re-renders.

Check out another example in the code sandbox below.

Edit znw4wjn914

Read more about React 16.6.0 here.

Catching errors in React (16 and up)

If an error is thrown while rendering React, React unmounts the entire tree.

In production, this might not be behaviour you want. The behaviour might be inconsequential to the user’s current path and why stop the user cold due to an unanticipated state?

React provides a function componentDidCatch to help manage exceptions and keep the consequence of the error localized to a specific part of your component tree.

This blog post describes the concept of an ErrorBoundary which can look like this:

class ErrorBoundary extends Component {
  componentDidCatch(error, {componentStack}) {
    console.log("error", error)
    console.log("componentStack", componentStack)
  }

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

Using the componentDidCatch lifecycle function this component will catch any error thrown by its children. If an error is thrown it will not render and none of it’s children will render, but all components in different sections of the component tree will render.

The second argumunent to componentDidCatch is an object containing a key called componentStack which is a nice stack provided by React.

H/T Josh Branchaud

Return value of snapshot in componentDidUpdate

React has a rarely used lifecycle method getSnapshotBeforeUpdate where you get the opportunity to look at your current DOM right before it changes.

The return value of this method is the third parameter of componentDidUpdate.

getSnapshotBeforeUpdate(prevProps, prevState) {
    return "value from snapshot";
}

componentDidUpdate(prevProps, prevState, snapshotValue) {
  console.log(snapshotValue);
}

The above code will output value from snapshot in the log.

The purpose for getSnapshotBeforeUpdate given in the React documentation is for making sure a value that can’t be set with rendering - like the scroll position - is exactly where you want it based on the state it was before the rerender.

My code example is here.

CSS !important Is Not Supported By Inline Styles

You can get pretty far with React’s inline styling of components. There are however a few limitations. One such limitation is that the !important clause is not supported.

If you try applying !important to an inline style like so:

<div style={{ color: "red !important" }}>
  My div
</div>

You’ll be disappointed when you open up the browser and inspect that div tag. The color rule will be ignored and excluded from the output html.

source

CRA Dependencies and ES5

At the time of this writing, create-react-app requires that all dependencies are precompiled to ES5. If they aren’t, the build will abort.

When this happened to me today, I followed a few of the recommended workarounds, eventually switching to a library that uses the older ECMA standard.

Running $ yarn build-js (or equivalent) when testing out any dependency would be a good way to surface this incompatibility upfront.

Here’s the Github issue on zipcodes-perogi where I documented my journey to this discovery.

Be specific in styled components with `&`

input[type="number"] has styles applied at a global level. I want to override some of those styles

a more specific selector takes precedence over a less specific one

So if I just define some css that is:

.myCoolInput {
  background-color: green;
}

It won’t override the more specific input[type="number"] selector’s background-color: pink.

So this styled component I have is wrong.

const GreenInput = styled.input`
    background-color: green;
`

render (
  <GreenInput type=["number"]/>
);

I need to use the & and move the className attribute to get more specificity for my style.

const GreenInput = styled(NumberInput)`
  input& {
      background-color: green;
  }
`

This outputs:

input.<GreenInputClassId> {
  background-color: green;
}

This selector has the same specificity as input[type="number"] but is declared later, so takes precedence.

Don't forget the `className` prop!!

So this is normal styled-component stuff:

const RedDiv = styled.div`
  color: red;
`

which outputs

<div class='<someClassId>'>
</div>

.someClassId { color: red; } 

And then you can wrap that div

const BlueDiv = styled(RedDiv)`
  color: blue;
`

which outputs:

<div class='<someClassId>'>
</div>

.someClassId { color: blue; } 

And you can also style existing components. If I have the component:

const CoolComponent = (props) => {
  render(
    <div></div>;
  )
};

const PurpleDiv = styled(CoolComponent)`
  color: purple; 
`

That outputs

<div></div>

It’s not purple!!

That’s because if you want to style non-styled-component components with styled-components then you have to use the className prop to set the class onto your element.

const CoolComponent = ({className}) => {
  render(
    <div className={className}></div>;
  )
};

Which outputs

<div class='<someClassId>'>
</div>

.someClassId { color: purple; } 

React-Router Location Hash #️⃣

Want to do cool things with the hash element of a URL, using React-Router? You can!

React-Router’s location contains a hash key:

{
  key: 'ac3df4',
  pathname: '/somewhere'
  search: '?some=search-string',
  hash: '#hereiam',
  state: {
    [userDefined]: true
  }
}

Anywhere location is available, pull off hash (defaults to an empty string) and have fun!

Change Styled Component via Parent state

Styled Components has been changing the way I think about CSS and styling. Including the css in with the component makes sense, you don’t have to hunt down styles or wonder what unravels when you start changing styles in a heavily nested scss document.

Today I learned that you can reference styled components in other components to allow parents to change the state of their children.

<Parent>
    <Thing/>
</Parent>

When a user hovers over Thing’s parent, I want to change it’s color from blue to red. I can do that by interpolating Thing into Parent.

const Thing = styled.div`
  color: blue;
`
const Parent = styled.div`
  &:hover {
    ${Thing} {
      color: red 
    }
  }
`

Run One Jest Test

When iterating on a test, I want to run just that test. This is a bit tricky with Jest, but it can be done.

First, tell the test framework to run a file, or a describe block:

 # Test file
$ yarn test path/to/test.test.js

# Describe block
$ yarn test -t 'myAssertiveDescribeBlock'

Then, add .only to your it or test block to run just that example:

it.only('is true', () => {
  // your test here
});

It’s just that easy.

only docs

Forcing A Child Remount With The Key Prop

There are a couple prop names that have reserved usage in React. One of those is key. We generally use key when we are rendering a list of things. It is a way of uniquely identifying each element in a list so that React minimizes re-rendering when parts of the list change.

We can flip this on its head and utilize key as a way of forcing a remount and re-render of a child component.

Imagine I have a component that does a number of things including rendering a component with some internal state, such as a counter.

class MyComponent extends React.Component {
  state = {
    remountKey: (new Date()).getTime(),
  }

  resetCounter = () => {
    this.setState({
      remountKey: (new Date()).getTime(),
    });
  }

  render() {
    return (
      <div>
        {/* some other stuff in my component */}

        <Counter key={this.state.remountKey} />
        <button onClick={this.resetCounter}>Reset Counter</button>
      </div>
    );
  }
}

I can force this Counter component to remount, thus resetting its state by passing it a new key value. The button can trigger this by updating the remountKey value.

Display line break content in React with just CSS

Let’s say you have an array of strings and you want to display its content inside a paragraph:

const locations = ['Location 1', 'Location 2']
<p>{locations.join('\n')}</p>

That will display all the items inline even if the line break is present :

Location 1 Location 2

Instead of adding unnecessary <br /> tags to the markup you can simply specify a css property to render line breaks:

<p style="white-space: pre-line">{locations.join('\n')}</p>

This will look like:

Location 1
Location 2

Extract a Shared Yup Validation

In a growing React app with Yup validations, schemas can become repetitive. But since the validation schema is a JavaScript object, we can extract single validations, or a group of validations.

Here’s my schema definition for an address form:

validationSchema: yup.object().shape(addressValidation)

And the address validation:

const addressValidation = {
  address: yup.string().required(),
  address2: yup.string(),
  city: yup.string().required(),
  state: yup.string().required(),
  zip: yup.string().required(),
};

Reuse this helper anytime you need to collect an address.

Spelunking Through Components With Enzyme's Dive

Most of the components we write have other components nested in them.

const Hello = ({ name }) => <h1>Hello {name}!</h1>;

const HelloContainer = (props) => (
  <div>
    <Hello {...props} />
  </div>
);

If we are to shallow render the above component using Enzyme, we’ll only see things one layer deep:

const wrapper = shallow(<HelloContainer name="World" />);
// wrapper ~= <div><Hello name="World" /></div>

If we’d like to explore a particular child of the rendered component further, we can do a little find and dive.

const wrapper = shallow(<HelloContainer name="World" />);
const helloWrapper = wrapper.find(Hello).dive();
expect(helloWrapper.text()).toEqual("Hello World!");

This allows us to make pinpoint assertions about how our components render without mounting the entire thing.

See a live example here.

h/t Vidal Ekechukwu

Use React 16 With Gatsby

Gatsby, the blazing fast static site generator for React, is tied to React 15.6. If you’ve been using React 16+ for a while, then this may come as a bit of a buzzkill.

Fortunately, there is a Gatsby plugin that lets you use React 16 with a Gatsby v1 site — gatsby-plugin-react-next.

Add it the plugin as a dependency:

$ yarn add gatsby-plugin-react-next

Then add it to the list of plugins in gatsby-config.js:

plugins: [`gatsby-plugin-react-next`];

source

Avoid The Notch With SafeAreaView On React-Native

iOS devices, especially the iPhone X, have areas of the screen that are cut off in ways that present quite a challenge to developers. One of the easiest ways to deal with rounded corners and the notch when developing for iOS is to avoid them all together.

The purpose of SafeAreaView is to render content within the safe area boundaries of a device.

import { SafeAreaView, View, Text } from 'react-native';

const App = () => {
  return (
    <SafeAreaView style={{ flex: 1, backgroundColor: "#e6e6e6" }}>
      <View>
        <Text>Safely rendered in the visible area of the device!</Text>
      </View>
    </SafeAreaView>
  );
}

The unsafe area can be styled however you like and everything inside <SafeAreaView> will be pushed into the visible area of the screen.

h/t Dillon Hafer

source

Dynamically Create HTML Elements

An HTML element can be created with a string that matches a recognized entity.

const Paragraph = 'p';
return <Paragraph>Some paragraph content</Paragraph>

This means we can dynamically create HTML elements such as headers:

const H = ({ level, ...props }) => {
  const Heading = `h${Math.min(level, 6)}`;
  return <Heading {...props} />;
};

return (
  <React.Fragment>
    <H level={1}>Header 1</H>
    <H level={2}>Header 2</H>
    <H level={5}>Header 5</H>
  </React.Fragment>
);

With some inspiration, I’ve created a live example here.

create-react-app Has A Default Test Setup File

In Configure Jest To Run A Test Setup File, I pointed to a way of configuring Jest in either the package.json or jest.config.js file with the setupTestFrameworkScriptFile value.

In a create-react-app project, this is not an option because setupTestFrameworkScriptFile is not one of the permitted config values for Jest.

There is a built-in value which happens to match what was recommended in the above post — <rootDir>src/setupTests.js.

This means that there is no configuration required. Instead, just create a setupTests.js file in the src directory of your CRA project and add any framework setup you need there. That file is already configured to run when you invoke yarn test.

source

Sync Your react-router State With Redux

If you are building a React app that uses both redux and react-router, you’ll find that you are managing app state in two places. Most of your app state is in redux. The router-specific state is component-state in the Router.

You can unify it all in redux with react-router-redux.

You’ll need to apply some middleware, combine routerReducer with the rest of your reducers, and then swap out your BrowserRouter with a ConnectedRouter. You can read about the details here.

Create Dynamically Named Custom React Components

A React element is as simple as a function that returns some valid JSX. Any function will do.

const CustomComponent = ({ children }) => {
  return (
    <React.Fragment>{children}</React.Fragment>
  );
};

This function provides us with a React component that has a fixed name — CustomComponent. With the help of the displayName property, we can create dynamically named components.

const ComponentGenerator = ({ customName, children }) => {
  const CustomComponent = ({ children }) => {
    return (
      <React.Fragment>{children}</React.Fragment>
    );
  };
  CustomComponent.displayName = customName;

  return (
    <CustomComponent>{children}</CustomComponent>
  );
};

const App = () => {
  return (
    <ComponentGenerator customName="RandomComponentName">
      Hello!
    </ComponentGenerator>
  );
}

If we inspect the generated React tree, we will not see anything called <CustomComponent>, but instead we will see our <RandomComponentName> component.

Remember, React components need to have an uppercase name.

Mock A Function That A Component Imports

You have a component that relies on an imported function, isAuthenticated().

// MyComponent.js
import React from 'react';
import { isAuthenticated } from './authentication';

const MyComponent = (props) => {
  if (isAuthenticated()) {
    return (<div>{/* ... */}</div>);
  } else {
    return (<div>Not authenticated</div>);
  }
};

You’d like to test that component without having to manage the authentication of a user. One option is to mock out that function. This can be done with some help from jest.fn() and the mock.mockReturnValue() function.

// MyComponent.test.js
// ... various testing imports

import * as authModules from './authentication';

it('renders the component', () => {
  authModules.isAuthenticated = jest.fn().mockReturnValue(true);

  const wrapper = shallow(<MyComponent />);
  expect(toJson(wrapper)).toMatchSnapshot();
});

By importing the same module and functions used by MyComponent, we are then able to replace them (specifically, isAuthenticated) with a mocked version of the function that returns whatever value we’d like. As MyComponent is being rendered, it will invoked our mocked version of isAuthenticated instead of the actual one.

You could test the other direction like so:

authModules.isAuthenticated = jest.fn().mockReturnValue(false);

Access The Latest Lifecycle Methods In An Old App

With the release of React 16.3 we have access to some new lifecycle methods and are in the first phase of what will eventually result in the deprecation and removal of componentWillMount, componentWillReceiveProps, and componentWillUpdate.

You may not be ready to move your project to React 16.3, but that doesn’t mean you can’t start migrating your lifecycle methods now. We’ll need a polyfill — react-lifecycles-compat.

import React from 'react';
import { pollyfill } from 'react-lifecycles-compat';

class MyComponent extends React.Component {
  static getDerivedStateFromProps(nextProps, prevState) {
    // ...
  }

  render() { ... }
}
polyfill(MyComponent)

export default MyComponent;

For any of our class components for which we’d like to start using the new lifecycle methods, we just need to import the polyfill function and then transform the class component with the polyfill before exporting it.

source

React StrictMode Component

Today I tried React’s new StrictMode component. Added in React 16.3, StrictMode is, according to the release notes:

[A] tool for highlighting potential problems in an application. Like Fragment, StrictMode does not render any visible UI. It activates additional checks and warnings for its descendants.

Here’s a heavy-handed way to implement it:

// index.js

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
document.getElementById('root')

In this scenario, App and all of its children get help with:

  • Identifying components with unsafe lifecycles
  • Warning about legacy string ref API usage
  • Detecting unexpected side effects

At the time of publishing this post, this will likely mean console warnings about the use of soon-to-be-deprecated lifecycle hooks such as componentWillMount. You’ll get these errors from dependencies, too, which is why we aren’t using StrictMode until at least all our dependencies have upgraded to the latest syntax.

Debug Jest Tests In create-react-app

When you put a debugger; statement in one of your Jest tests and run yarn test, the test runner will ignore the debug statement and run to completion. This is because Jest defaults to parallelizing tests which won’t mix well with manual debugging intervention.

If we want to be able to run our Jest tests through a debugger. We will need two things. First, we need a debugging environment — Chrome’s devtools will work well for this. Second, we need our tests to run in band. The react-scripts documentation recommends adding a second, debug-specific test command to your package.json:

"scripts": {
  "test:debug": "react-scripts --inspect-brk test --runInBand --env=jsdom"
}

You can now run yarn test:debug which will start a paused debug session. Open chrome at chrome://inspect to access and open the debugging session panel. Now, debug away.

Building A React App In The Browser

There are a couple relatively new tools that give you just about everything you need to build a React app in the browser.

The first is CodeSandbox. It has a couple pre-configured environments for different technologies. The main one is React. CodeSandbox describes itself as such:

an online editor that helps you create web applications, from prototype to deployment.

The second, which just released out of beta, is Glitch which comes with projects like react-starter so that you can jump into a React project that is ready to roll. Glitch describes itself as:

the friendly community where you’ll build the app of your dreams

You can quickly build and publish React apps in either of these and share them with the world.

Test Files In create-react-app

Any .js files placed in the __tests__ directory will be treated as tests by Jest when running yarn test. If you don’t want to place all of your files in that directory and especially if you want to co-located your test files with the source files, you can name them with the .test.js or .spec.js suffixes.

Any files in your create-react-app project ending in these suffixes will be treated by Jest as test files and included in test runs.

There are more details in the docs.

Firing the blur event when a component unmounts

If an input is focused and a React component containing that input unmounts, then the blur event for that input will not fire. If you use the blur event to save the contents of the input then you are in trouble.

You can know what input/select on the page currently has the focus with document.activeElement. Combine that with the component lifecycle method componentWillUnmount() to fire blur for the currently focused element on unmount.

componentWillUnmount() {
  const input = document.activeElement()
  input.blur()
}

The documentation for activeElement says it may return <body> or null when there is no active element, so you need to protect against that.

componentWillUnmount() {
  const input = document.activeElement()
  if (input) {
    input.blur()
  }
}

Enforce Specific Values With PropTypes

Being able to constrain our user interfaces to very specific values is valuable. This makes our interfaces easier to reason about and easier to test. PropTypes in general are one of the ways that we constrain our UIs. We can go even further than simple type constraints by limiting a prop to a specific set of values, an enum if you will.

MyComponent.propTypes = {
  flavor: PropTypes.oneOf(['Vanilla', 'Chocolate', 'Strawberry']),
};

The docs say about oneOf():

You can ensure that your prop is limited to specific values by treating it as an enum.

If we use MyComponent with a value such as Pistachio, we’ll have a console warning to answer for.

source

A React Component Is Just A Bag Of Data

If you write enough React using JSX, it is easy to forget that you’re not working with markup. Everything — divs, h1s, 3rd party components, your components — all get boiled down to JavaScript objects full of data.

Any given React component is really just a bag of data. Try doing a console.log to see. Here is an example from an earlier post.

const ParentWithClick = ({ children }) => {
  return (
    <React.Fragment>
      {React.Children.map(children || null, (child, i) => {
        console.log(child);
        return <child.type {...child.props} key={i} onClick={handleClick} />;
      })}
    </React.Fragment>
  );
};

const App = () => (
  <div>
    <ParentWithClick>
      <span>Click this span</span>
    </ParentWithClick>
  </div>
);

Looking in the console, we see the following output:

Object {type: "span", key: null, ref: null, props: Object, _owner: Object…}
 type: "span"
 key: null
 ref: null
 props: Object
  children: "Click this span"
 _owner: Object
 _store: Object

It contains information about the component itself and because of the tree structure of this data, you could potentially expand the props —> children sections several times for certain components.

See a live example here.

Mapping Over One Or Many Children In React

In Dynamically Add Props To A Child Component, I talked about how a child element can be reconstituted with additional props. The approach I showed will only work in the case of a single child being nested in that component. What if you want your component to account for one, many, or even children?

React comes with a built-in function for mapping that handles these cases.

const ParentWithClick = ({ children }) => {
  return (
    <React.Fragment>
      {React.Children.map(children || null, (child, i) => {
        return <child.type {...child.props} key={i} onClick={handleClick} />;
      })}
    </React.Fragment>
  );
};

The React.Children.map function allows mapping over one or many elements and if children is null or undefined, it will return null or undefined respectively.

See a live example here.

Dynamically Add Props To A Child Component

If your component has an element nested in it, then it will receive a children prop. There are a number of things you can do beyond simply including the children as part of the rendered output of the component. One thing you can do is put additional props on the child.

const ParentWithClick = ({ children }) => {
  return (
    <children.type
      {...children.props}
      onClick={() => alert("You clicked me!")}
    />
  );
};

This ParentWithClick component will reconstitute its child component with its given props and a new onClick prop.

Here is how it can be used:

const App = () => {
  return (
    <ParentWithClick>
      <span>Hello!</span>
    </ParentWithClick>
  );
};

Click on Hello! and you’ll see the alert.

Minor caveat: multiple children and a string child will need to be handled differently.

See a live example here.

Who Is Your Favorite Child?

When we put some content inside the open and close tags of one of our components, we get access to it as the children prop.

const Parent = ({children}) => {
  return (
    <React.Fragment>
      <p>These are my favorites:</p>
      {children}
    </React.Fragment>
  );
}

const App = () => (
  <div>
    <Parent>
      Greg and Marsha
    </Parent>
  </div>
);

What happens if we also provide an explicit children prop to Parent?

const App = () => (
  <div>
    <Parent children={"Jan and Peter"}>
      Greg and Marsha
    </Parent>
  </div>
);

Which will take precedence when we destructure children in the parent component?

In the example above, we’ll still see Greg and Marsha rendered. The content placed inside the tags will take precedence over the explicit children prop.

See a live example here.

React Router Route can call a render function

In General, the React Router Route component looks like this.

<React path="/triangles" component={Triangles} />

If the current url matches /triangles then the Triangles component will render.

If you want to render something simple however, you can pass down a function with the render prop.

<React path="/helloworld" render={this.renderHelloWorld} />

With that function defined in the same parent component and returning some jsx

renderHelloWorld = () => {
  return (
    <div>Hello World!</div>
  )
}