Skip to content

Instantly share code, notes, and snippets.

@spaceplesiosaur
Last active January 15, 2020 15:29
Show Gist options
  • Save spaceplesiosaur/7293296b7bdf481d3b6201a15e49874a to your computer and use it in GitHub Desktop.
Save spaceplesiosaur/7293296b7bdf481d3b6201a15e49874a to your computer and use it in GitHub Desktop.

Adapted from Kirk Veitch's gist https://gist.github.com/KVeitch/01b0685bf8e1574b19f2c17be0730b9b

Creating a React App

  1. In the terminal run: npx create-react-app NAME-OF-APP
  1. Cd into the new directory: cd NAME-OF-APP

  2. Run: npm install.

  3. You can run npm start to see if the app was set up correctly, or you can check Atom.

Setup Redux

  1. npm i redux react-redux redux-devtools-extension -S
  • redux - Allows us to have access to using Redux in our app.
  • react-redux - Allows us to connect our react components to our Redux store.
  • redux-devtools-extension - Useful for debugging in our devtools
  1. In src/index.js

import { Provider } from 'react-redux';

  • a component from react-redux that wraps our App component and allows each child component to be connected to the store

import { createStore } from 'redux';

  • a function from Redux that uses the rootReducer to create the store

import { composeWithDevTools } from 'redux-devtools-extension';

  • a method we brought in and can pass as an argument with createStore so that we have access to our devtools and can view our store.

import { rootReducer } from './reducers';

  • our combined reducers to create our store

(order matters here)


ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);
  1. In actions/index.js
  1. In src/reducers/index.js
import { combineReducers } from 'redux';
import { todos } from './todos';

export const rootReducer = combineReducers({
  todos: todos
});

**Where we define the properties that will exsits in our global store

  1. npm i redux-thunk -S

Getting the backend up

Setup Backend

  1. Clone down repo, make sure you are not nested in project directory

  2. Globally install nodemon. Runs the server - haven't done this before so hopefully won't need to. npm install nodemon -g

  3. cd into repo. Make sure you're in the right repo!! That's where you get access to the API's on local host 3001

  4. Run npm install

  5. Run npm start

  6. Use Postman to checkout the data!

Start making components

  1. Make a components folder

  2. Make a folder for each component -Should contain the js file (Name.js) -Should contain the css file (Name.css) -Should contain the test file (Name.test.js) (Also! make a folder for your apiCalls.js and its testing file)

  3. Each js component needs imports! -Make sure to import React from 'react' -If it's a class component, it's extending Component, so be sure to import React, { Component } from 'react'; -Make sure to import the .css file import './Name.scss' -Import all the components it will be loading import ChildComponent from '../../ChildComponent/ChildComponent'

  4. Build components. Remember, you'll get a big ass error if nothing is in one of your imported components, so if something is imported, you'd better build it!

  5. Turnary notes. Remember, only one statememnt on each side of the turnary!

render() { return ( (this.state.error !== '') ? <Child /> : <section className="render-box"></section> )}

Some notes on file structure and importing

Enoent means it can't find a file

If you move App into a components folder, which is in a src folder, and index is in that srx folder, you will need to import it into index like this: import App from './components/App/App';

Files in the components folder will call each other like this: import RenderArea from '../Form/Form'

.. means that you're going up to your parent folder. You can only do two dots at a time. For instance, this means out of my folder, into another folder that's in the same parent as my folder: '../File/File'

/ means you're going into another file. ../../some-parent/file.js means you're leaving your parent folder, then you're leaving that folder's parent, and now you're going into another folder within the great-grandparent folder.

import images using the variable you're going to name the image and the file name you're importing it from. Then use the variable name in brackets! img src={imageName} . import imageName from '../../images/picture.png'

Build Components

  1. class components will be structured like this:

(notes) //make sure to export default at the beginning so that it exports to other files! It also needs to extend "Component," which is a parent class that contains some of the methods we need to use.//

`componentDidMount = () => { //this is a method that runs right when the component is finished mounting - put functionality that you'd like to run right when the component is finished loading here. This is a great place for your get fetch calls// }

//Put in the rest of the methods you need here. In ES6, you structure them as below:

functionName = (optionalArgument) => { //do stuff. Make sure to return if you aren't actively doing something else, like setting state!// }

//Below is the render method. This should be the last method on your class, and after the return, add () and instert your JSX. This is probably the only place your JSX will live, and really SHOULD be the only place your JSX will live.

render() { return (

//add various elements here. Make sure whateer JSX you have is enclosed in an enclosing element! {this.state.exampleStartingState} //Anything javascript-shaped, like calling from state or calling one of your methods, will be enclosed in brackets// ) } }`

  1. Functional components will look like below - please note, they do not have states! They can get and pass down props.

Add in CSS

This is going to be per your preference, but if you're building an ideabox you'll probably have to use Grid. Don't forget to use className, not class, in your elements!

API Calls

By now you're probably ready to do a GET call to get some data. First, check the endpoint to see where the data is coming from and how to format it. You will need to structure your app accordingly, so it might be something good to do early on.

See helpful scripts at bottom of gist for examples.

Setting state

this.setState({key: newValue})

Sometimes, if it's an array, you'll need to do this: this.setState({ key: [ ...this.state.array, { value: event.target.previousSibling.value, id: Date.now() }]}) This essentially sets up the state with a copy of its old self, plus the new thing being added.

Your states will be passed down as props to your next component.

Props

Set them in your child component like this if it's a class. If it's a function, just use props.something: (<NameCard prop1={this.props.thing} prop1={this.state.thing} />)

Events and buttons

For buttons, don't call the function with parens. It's a callback. If it needs an argument, wrap that in another function and use that as the callback. onClick={() => funcionFromProps(argument)}

In JSX, call functions with the (). You are invoking them.

Setting Up Testing

  1. Install Enzyme: npm i enzyme -D
  2. Install Enzyme Adapter: npm install enzyme-adapter-react-16 -D
  3. Create setupTests.js inside of /src create setupTests.js: touch src/setupTests.js
  4. Add these 3 lines of code into setupTests.js

import { configure } from 'enzyme'; import Adapter from 'enzyme-adapter-react-16';

configure({ adapter: new Adapter() });

  1. For snapshot testing, install Enzyme Wrappers:

npm install enzyme-to-json -D

  1. Add serializer to package.json:

"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';

If dealing with something connected to redux,

import { Provider } from 'react-redux'; import { getInfo } from '../../util/apiCalls.js'; import { setAction } from '../../actions/index.js'; import { setOtherAction } from '../../actions/index.js'; import { App, mapStateToProps, mapDispatchToProps } from './App';

Main difference here is that the unconnected component won't be your default export, so you need to export it in brackets. Also, export { Provider }!

Check test coverage

If you want to check your test coverage, run npm test -- --coverage --watchAll=false

Update snapshots

If you can't just hit u, run npm test --updateSnapshot

Testing

Some notes:

-if we have conditional rendering we need to write snapshots for all conditions -if we want to check that class methods were called in any component rendering we can use jest.spyOn on it -if we have any events we need to test that something is called after this event was occurred -componentDidMount (without called fetch) just testing if any function (inside it) has been called -for componentDidMount (with) we use fetch as a function from other document, create mock async function for it and check that this function has been called (example in App component test)

https://gist.github.com/spaceplesiosaur/f96b0bce8d74aad40558fd47a7bf10f3

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
  4. Turing Mod3 Linter

Setting Up Router

  1. In terminal, run npm i react-router-dom -S
  2. In index.js, add import { BrowserRouter as Router } from 'react-router-dom'
  3. Wrap in

`import { rootReducer } from './reducers/index'; import { createStore } from 'redux'; import { Provider } from 'react-redux'; import { composeWithDevTools } from 'redux-devtools-extension'; import { BrowserRouter as Router } from 'react-router-dom'

const store = createStore(rootReducer, composeWithDevTools())

ReactDOM.render( , document.getElementById('root'));`

Setting Up Redux

  1. In terminal, run: npm i redux react-redux redux-devtools-extension -S
  2. In index.js js, import

import { BrowserRouter as Router } from 'react-router-dom'

  1. Wrap App in Provider and create store: `const store = createStore(rootReducer, composeWithDevTools());

ReactDOM.render( , document.getElementById('root') ); `

Setting Up SASS

  1. In terminal, run: npm install --save-dev sass . I think. See if it works on your project.

Common Errors:

Error: Reducer "seasons" returned undefined during initialization. If the state passed to the reducer is undefined, you must explicitly return the initial state. The initial state may not be undefined. If you don't want to set a value for this reducer, you can use null instead of undefined.

Did you make the state argument in the reducer state: [] instead of state = [] again??

Helpful scripts

GET:

export const getData = (url, type) => { return fetch(url) //-return the fetch! .then(response => { if (!response.ok) { throw Error(${type} wasn't imported) } return response.json() //-You will be fetching the URL. PUT IT IN QUOTES The next thing you do is jsonify the response so the browser can read it }) } -return the fetch! -You will be fetching the URL. PUT IT IN QUOTES The next thing you do is jsonify the response so the browser can read it

  • make an if/else for the Error - the error will force it to drop into the catch. The optional second argument goes there.
  • Best to put it in a separate file and call it import { getData ) from '../apiCalls/apiCalls. Then do getData('httpurl', 'thing)
  • chain .thens onto it. Likely, you will be mapping over the data and putting it into state as some kind of array. GET:

export const getInfo = (url, type) => { return fetch(url, options) .then(response => { if(!response.ok) { throw Error (Oh no! There is a problem finding ${type}) } return response.json() }) }

POST:

`postNewThing = (newThing) => { const options = { method: 'POST', body: JSON.stringify( {id: 5, ...newThing} //here I am using the spread operator on the newThing. The thing being stringified in the body MUST be in the format the docs specify!!// ), headers: { 'Content-Type': 'application/json' } }

return fetch('http://localhost:3001/api/v1/users', options) //just like a get, but add in options.  It will know it's a post because it looks at this object
        .then(res => {
          if(!res.ok) {
            throw Error('Something is not right, try again later')
          }
          return res.json()})
          .then(data => ({stateThing: [...this.state.stateThing, data]})) //make sure you do something with the returned response!!

} `

Delete:

      const options = {
        method: 'DELETE',
        headers: {
          'Content-Type': 'application/json'
        }
      }//No need for a body here!//

      fetch(`http://localhost:3001/api/v1/users/${id}`, options) //make sure there is a slash!
        .then(() => fetch('http://localhost:3001/api/v1/users'))
        .then(response => response.json())
        .then(data => this.setState({stateThing: [...this.state.stateThing, data] })) //be sure to reset the state
        .catch(error => this.setState({ error: error.message }));
    }```

Promise.all :

`const setOfInfo =
        Promise.all([firstFetch, secondFetch, thirdFetch])
        .then(info => {return {one: info[0], two: info[1].moreDeeper, three: info[1].evenDeeper, four: info[2]}})
        .then(stateValue => {this.setState({stateThing: [...this.state.stateThing, stateValue]})})
      }))`
 
 Example of a very nested turnary:
 
 `render() {
    return (
      (this.state.error !== '')
        ? <Child />
        : <section className="render-box">
          {(this.state.isLoaded)
            ? this.generateWidgets()
            : <OtherChild />
          }
          <div className="button-set">
            {
              this.state.fetchNumber > 10 &&
              (<button id="less-btn" onClick={this.decreaseFetchNumber}>less</button>)
            }
            {
              this.state.widgets &&
              this.state.fetchNumber <= this.state.widgets.length &&
              (<button id="more-btn" onClick={this.increaseFetchNumber}>more</button>)
            }
          </div>
        </section>
    )
  }
}`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment