Skip to content

Instantly share code, notes, and snippets.

@aemcdonald
Forked from polsieira/react-app-setup.md
Created October 20, 2020 22:40
Show Gist options
  • Save aemcdonald/67f2600a58f928891fba690c84975c1c to your computer and use it in GitHub Desktop.
Save aemcdonald/67f2600a58f928891fba690c84975c1c to your computer and use it in GitHub Desktop.

Exam Checklist

Mid-Mod Notes

Time: 3 hrs

  • Spec like IdeaBox
  • Build out an app from a boilerplate / work through iterations
  • Dependencies installed
  • 2 repos (1 FE and 1 BE)
  • Bring in data from an API
  • Display data, build out a form
  • Don’t worry about nested fetch requests
  • Don’t worry about React Router

Review:

  • Fetch (GET, POST, DELETE)

Testing:

  • Snapshot
  • Changes in state
  • Testing methods
  • Event simulation tests
  • Extension: Async testing

You can use your notes and lessons, and google! You can’t look at previous code!

Creating a React App

  1. In the terminal run npx create-react-app NAMEOFYOURAPP
  2. Cd into the new directory: cd NAMEOFYOURAPP
  3. Run npm install.
  4. You can run npm start to see if the app was set up correctly.

Setting Up Testing

  1. Install enzyme: npm i enzyme -D
  2. Install enzyme-adapter-react-16: npm install enzyme-adapter-react-16 -D
  3. Inside of /src create setupTests.js: touch src/setupTests.js
  4. Put the following 3 lines of code in setupTests.js
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

configure({ adapter: new Adapter() });
  1. For snapshot testing, install enzyme-to-json npm install enzyme-to-json -D
  2. In package.json, add the following lines of code:
  "jest": {
    "snapshotSerializers": [
      "enzyme-to-json/serializer"
    ]
  }

Don't forget the comma!

  1. Add an extra line in App.js (just so there's a change in the file) and save. Then run npm test to check that the files are connected correctly.
  2. Include the following lines as headers for all test files:
import React from 'react';
import { shallow } from 'enzyme';
import ClassName from './ClassName';

Boilerplates

Class Component

import React, { Component } from 'react';
import Classname from './Classname';
import Funcname from '../Funcname/Funcname';
import './App.css';

class App extends Component {
  constructor() {
    super();
    this.state = {
      key: value
    }
  }
  
  method = () => {
    console.log('run something')
  }
  
  render() {
    return (
      <div>
        Return something
      </div>
    )
  }
}

export default App;

Functional Component

import React from 'react';
import anotherFuncname from '../anotherFuncname/anotherFuncname';
import './Funcname.css';

const Funcname = () => {
  return (
    <div>
      Return something
    </div>
  )
}

export default Funcname;

setState

.then(ideas => this.setState({
        ideas: ideas
      }))
      
.then(ideas => this.setState({ ideas }))

this.setState({
    value: key
  })

setState Is Async

functionName = (parameter) => {
  this.setState({
    value: key
  }, () => {
    anotherFunctionName((parameter) => {
     return something
   }))
  });
 };

Snapshot Test

import React from 'react';
import { shallow } from 'enzyme';
import NameOfTested from './NameOfTested';

describe('NameOfTested', () => {
  it('should', () => {
    const wrapper = shallow(
      <Component />
    )
    expect(wrapper).toMatchSnapshot();
  })
})

Test Dynamic Changes Template

Setup - What do we need to do in order to render the component (aka shallow or mount). What data needs to be mocked?
Execution - Let’s run the command or simulate the action.
Expectation - This is where our assertion happens. After running our function, what did we expect to happen?

Update State Test

import React from 'react';
import ReactDOM from 'react-dom';
import { shallow } from 'enzyme';
import NameOfTested from './NameOfTested';

describe('NameOfTested', () => {
  it('should', () => {
    const wrapper = shallow(
      <Component />
    )
   const mockData = ?
   const expected = ?
   
   wrapper.instance().functionName(mockData);

   expect(wrapper.state('stateKeyName')).toEqual(expected);
  })
})

Function Test

it('should change state based on an event', () => {
    const mockFunction = jest.fn()
    const wrapper = shallow(<
      Component 
      submitUser={mockFunction}
    />)
    const mockEvent = {
      target: {
        name: 'name', value: 'value' 
      } 
    }

    wrapper.instance().handleChange(mockEvent)
    expect(wrapper.state('name')).toEqual('value')
  })

Simulate A Click Test

wrapper.instance().forceUpdate();

wrapper.find('element').simulate('click');
wrapper.find('element').at(0).simulate('click');
wrapper.find('button').simulate('click', mockEvent);

expect(mockFunction).toHaveBeenCalledWith(argument);
expect(wrapper.instance().resetInputs).toHaveBeenCalled();

Ex:
it('should run funcName on click', () => {
    const mockFuncName = jest.fn();
    const wrapper = shallow(
      <Component
        prop={look to parent to see what gets passed down}
        prop={look to parent to see what gets passed down}
      />
    )
    wrapper.find('element').at(0).simulate('click');

    expect(mockFuncName).toHaveBeenCalled();
  });

Mock A Function

const mockFunction = jest.fn();

Mock An Event

const mockEvent = {
  preventDefault: jest.fn();
}

Mock Date.now()

global.Date.now = jest.fn().mockImplementation(() => 12345)

const expected = { title: '', description: '', id: 12345 };

Debug

console.log(wrapper.debug());

Verbose In package.json, under scripts, edit the "test" script:

"test": "react-scripts test --verbose"

Test Percentage Chart In Terminal

npm test --  --coverage --watchAll=false

Getting Your Site on GitHub Pages

Preparation

  1. Checkout your master branch and double-check to make sure it is up-to-date and there is nothing needed to be committed at this point
  2. Navigate to the root of your project directory
  3. Open your project in your text editor
  4. Double-check that all image tags in your HTML have the src attribute have ./ to start the path, e.g src="./images/turing-logo.png"

Required Steps

  1. In the package.json file, within the "repository" object, edit the "url" value to be "git+https://github.com/USERNAME/REPONAME.git" where USERNAME and REPONAME are replaced with your GitHub username and your repository name, respectively

OR NOT?

  1. In the package.json file, add the line: "homepage": "http://USERNAME.github.io/REPONAME", where USERNAME and REPONAME are replaced with your GitHub username and your repository name, respectively

DO THIS "homepage": ".",

  1. Add these two lines to the scripts section of the package.json file:
"predeploy": "npm run build",
"deploy": "gh-pages -d build"
  1. In the terminal, run npm install --save-dev gh-pages
  2. You should see these lines of JSON added to your package.json file:
"devDependencies": {
"gh-pages": "^1.1.0"
}
  1. Run npm run build in the command line
  2. Run npm run deploy in the command line

All of this will create a gh-pages branch in your repo with the contents from the build directory.

If you go to the GitHub pages site (http://USERNAME.github.io/REPONAME) in a minute, you should see your app! If not, check out the console to see what errors you're getting and troubleshoot from there.

Deploying with React Router

  1. npm install --save gh-pages
  2. “homepage”: “.“,
  3. “predeploy”: “npm run build”, “deploy”: “gh-pages -d build”
  4. import { HashRouter as Router } from ‘react-router-dom’ in index.js file and change to just <Router></Router>
  5. run npm run and build commands, done, go check GitHub Pages.

When You Make New Changes

If you make new changes to your master branch, GitHub Pages doesn't automatically know about these changes, and your site won't be up-to-date. You need to update the gh-pages branch to see those changes live on GitHub Pages. Here is how to update and keep everything in sync:

  1. After you're done making changes, checkout your master branch and double-check to make sure it is up-to-date and there is nothing needed to be committed at this point
  2. Run npm run build in the command line
  3. Run npm run deploy in the command line

Fetch and Post

  componentDidMount() {
    fetch('URL')
      .then(response => response.json())
      .then(data => this.setState({
        key: data
      }))
      .catch(error => console.error(error))
  }

  addIdea = varName => {
    const options = {
      method: 'POST',
      body: JSON.stringify(varName),
      headers: {
        'Content-Type': 'application/json'
      }
    };

    fetch('URL', options)
      .then(response => response.json())
      .then(data => this.setState({
        key: [...this.state.key, data]
      }))
      .catch(error => console.error(error))
  }

  removeIdea = id => {
    const options = {
      method: 'DELETE',
      headers: {
        'Content-Type': 'application/json'
      }
    }

    fetch(`URL/${id}`, options)
      .then(() => fetch('URL'))
      .then(response => response.json())
      .then(data => this.setState({ 
        key: data
      }))
      .catch(error => console.error(error));
  }
}

Form

Functions needed

handleChange = event => {
    this.setState({
      [event.target.name]: event.target.value
    })
  }

  submitNewData = event => {
    event.preventDefault()
    const newData = {
      match key/value pairs of required data
    }
    this.props.funcFromApp(newData)
    this.clearInputs()
  }

  clearInputs = () => {
    this.setState({
      key1: '',
      key2: ''
    })
  }

Input Structure

<input
  name=''
  placeholder=''
  value={this.state.key}
  onChange={() => {}}
/>

propTypes

Card.propTypes = {
  title: PropTypes.string.isRequired
}

https://reactjs.org/docs/typechecking-with-proptypes.html#react.proptypes

Backend Setup

git clone [https://github.com/ REPO NAME THEY GIVE US]
cd [CD INTO REPO]
npm install nodemon -g
npm i
npm start

Note that the frontend should be running on localhost:3000 and the backend should be running on localhost:3001.

Clone down the backend repo - but NOT inside your existing frontend repository!

Spread An Array

ideas: [...this.state.key, newVariableElement]

Original Sources

React I: The What and The Why https://frontend.turing.io/lessons/module-3/react-i.html

React II: The How, building IdeaBox https://frontend.turing.io/lessons/module-3/react-ii.html

React III: Workshop Ideabox With A Backend https://frontend.turing.io/lessons/module-3/react-iii.html

Unit Testing React Components https://frontend.turing.io/lessons/module-3/unit-testing-react.html

Get Your Site On GH Pages https://github.com/turingschool-examples/webpack-starter-kit/blob/master/gh-pages-procedure.md

Webpack Starter Kit https://github.com/turingschool-examples/webpack-starter-kit

Network Request GET/POST Requests https://frontend.turing.io/lessons/module-3/network-request-exercises.html

All Lessons https://frontend.turing.io/lessons/

Extras

Setting Up ESLint

  1. ESLint is already built in with create-react-app. Installing another eslint will likely break things.
  2. Add a script called "lint": "eslint src/" in your package.json (in the scripts object)
  3. In your terminal, run: npm run lint

Install SCSS/Sass

  1. Run: npm install node-sass --save
  2. Add variable file: touch src/variables.scss
  3. Change all .css file extentions to .scss
  • index.css App.css => index.scss App.scss
  1. Remeber to update the file paths anywhere the style files are being imported
  2. Add: @import './src/variables.scss'; To any .scss files you want to uses variables
  3. Format for creating variables: $var-name: var-value;

Any time .toHaveBeenCalledWIth() is called it needs to be used with a mock function so it can be spied on.

apiCalls testing


describe('apiCall', () => {
  const url = 'http://localhost:3001/api/v1/users';
  const newUser = {
    name: 'Pol',
    email: 'email@email.com',
    password: 'password',
  };
  const options = {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(newUser)
  };
  const mockResponse = {
    type: 'cors',
    url: 'http://localhost:3001/api/v1/users',
    redirected: false,
    status: 201,
    ok: true
  };

  it('should call fetch with specified url', () => {
    window.fetch = jest.fn().mockImplementation(() => {
      return Promise.resolve({
        ok: true,
        json: () => Promise.resolve(mockResponse)
      });
    });
    attemptCreateUser(newUser);

    expect(window.fetch).toHaveBeenCalledWith(url, options);
  });

  it('should return error if response not okay', () => {
    window.fetch = jest.fn().mockImplementation(() => {
      return Promise.resolve({
        ok: false
      });
    });

    expect(attemptCreateUser(newUser)).rejects.toEqual(
      Error('That username is already associated with another account. Try another email address.')
    );
  });

  it('should return an error if the server is down', () => {
    window.fetch = jest.fn().mockImplementation(() => {
      return Promise.reject(Error('500 Internal Server Error'));
    });

    expect(attemptCreateUser(newUser)).rejects.toEqual(Error('500 Internal Server Error'));
  });
});

describe('attemptLoginUser', () => {
  const url = 'http://localhost:3001/api/v1/login';
  const user = {
    email: 'email@email.com',
    password: 'password',
  };
  const options = {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(user)
  };
  const mockResponse = {
    type: 'cors',
    url: 'http://localhost:3001/api/v1/login',
    redirected: false,
    status: 201,
    ok: true
  };

  it('should call fetch with specified url', () => {
    window.fetch = jest.fn().mockImplementation(() => {
      return Promise.resolve({
        ok: true,
        json: () => Promise.resolve(mockResponse)
      });
    });
    attemptLoginUser(user);

    expect(window.fetch).toHaveBeenCalledWith(url, options);
  });

  it('should return error if response not okay', () => {
    window.fetch = jest.fn().mockImplementation(() => {
      return Promise.resolve({
        ok: false
      });
    });

    expect(attemptLoginUser(user)).rejects.toEqual(
      Error('Email does not exist or password is incorrect')
    );
  });

  it('should return an error if the server is down', () => {
    window.fetch = jest.fn().mockImplementation(() => {
      return Promise.reject(Error('500 Internal Server Error'));
    });

    expect(attemptLoginUser(user)).rejects.toEqual(Error('500 Internal Server Error'));
  });
});```

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment