mirror of
https://github.com/microsoft/frontend-bootcamp.git
synced 2026-01-26 14:56:42 +08:00
Merge branch 'master' into js-demo
This commit is contained in:
26
README.md
26
README.md
@@ -8,7 +8,7 @@ This repo is the content for a two day workshop focused on introducing you to th
|
||||
|
||||
## Who is this workshop for
|
||||
|
||||
This workshop starts as a very high level introduction to the core principles of the web: HTML, CSS and JavaScript. This section is targeted at new and experienced developers alike. The second day dives into more complex topics such as Typescript and state management within an application. The examples should be accessible to anyone, but you will get more out of the day if you have some foundational experience in programing or web technologies.
|
||||
This workshop starts as a very high level introduction to the core principles of the web: HTML, CSS and JavaScript. This section is targeted at new and experienced developers alike. The second day dives into more complex topics such as TypeScript and state management within an application. The examples should be accessible to anyone, but you will get more out of the day if you have some foundational experience in programing or web technologies.
|
||||
|
||||
## Web Workshop Setup
|
||||
|
||||
@@ -17,7 +17,8 @@ This workshop starts as a very high level introduction to the core principles of
|
||||
- Current version of [Node/NPM](https://nodejs.org/en/)
|
||||
- Install [Git](https://git-scm.com/downloads)
|
||||
- [VSCode Editor](https://code.visualstudio.com)
|
||||
- Up to date browser with dev tools (some demos will be shown using Chrome)
|
||||
- Up to date browser with React Developer Tools
|
||||
- React Developer Tools: [Chrome](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en) or [Firefox](https://addons.mozilla.org/en-US/firefox/addon/react-devtools/)
|
||||
|
||||
### Getting Set Up
|
||||
|
||||
@@ -25,11 +26,11 @@ This workshop starts as a very high level introduction to the core principles of
|
||||
$ git clone https://github.com/Microsoft/frontend-bootcamp.git
|
||||
$ cd frontend-bootcamp
|
||||
$ npm install
|
||||
|
||||
|
||||
// For Day 1, Steps 1-3
|
||||
$ npm run static
|
||||
$ npm run static
|
||||
// For the rest of the steps
|
||||
$ npm start
|
||||
$ npm start
|
||||
```
|
||||
|
||||
## Table Of Contents
|
||||
@@ -48,13 +49,13 @@ This workshop starts as a very high level introduction to the core principles of
|
||||
- [Exercise](step1-03/exercise)
|
||||
4. [React Introduction](step1-04)
|
||||
- [Demo](step1-04/demo)
|
||||
5. [Thinking in React: Building a Static Page](step1-05)
|
||||
5. [Building a Static Page](step1-05)
|
||||
- [Demo](step1-05/demo)
|
||||
- [Exercise](step1-05/exercise)
|
||||
6. [Thinking in React: State Driven UI](step1-06)
|
||||
6. [State Driven UI](step1-06)
|
||||
- [Demo](step1-06/demo)
|
||||
- [Exercise](step1-06/exercise)
|
||||
7. [Thinking in React: UI Driven State](step1-07)
|
||||
7. [Types & UI Driven State](step1-07)
|
||||
- [Demo](step1-07/demo)
|
||||
- [Exercise](step1-07/exercise)
|
||||
|
||||
@@ -62,7 +63,7 @@ This workshop starts as a very high level introduction to the core principles of
|
||||
|
||||
Demo and Exercises are combined
|
||||
|
||||
1. [Introduction to Typescript](step2-01)
|
||||
1. [Introduction to TypeScript](step2-01)
|
||||
2. [UI Fabric Component Library](step2-02)
|
||||
3. [Theming and Styling](step2-03)
|
||||
4. [Testing with Jest](step2-04)
|
||||
@@ -78,6 +79,13 @@ Demo and Exercises are combined
|
||||
- [React Docs](https://reactjs.org/docs/getting-started.html)
|
||||
- [Thinking in React](https://reactjs.org/docs/thinking-in-react.html)
|
||||
|
||||
### Follow the Authors!
|
||||
|
||||
If you are interested in JavaScript, TypeScript, React, Redux, Design Systems, follow us on Twitter:
|
||||
|
||||
- [@kenneth_chau](https://twitter.com/kenneth_chau)
|
||||
- [@micahgodbolt](https://twitter.com/micahgodbolt)
|
||||
|
||||
# Contributing
|
||||
|
||||
This project welcomes contributions and suggestions. Most contributions require you to agree to a
|
||||
|
||||
11
assets/scripts.js
Normal file
11
assets/scripts.js
Normal file
@@ -0,0 +1,11 @@
|
||||
// prettier-ignore
|
||||
var appInsights=window.appInsights||function(a){
|
||||
function b(a){c[a]=function(){var b=arguments;c.queue.push(function(){c[a].apply(c,b)})}}var c={config:a},d=document,e=window;setTimeout(function(){var b=d.createElement("script");b.src=a.url||"https://az416426.vo.msecnd.net/scripts/a/ai.0.js",d.getElementsByTagName("script")[0].parentNode.appendChild(b)});try{c.cookie=d.cookie}catch(a){}c.queue=[];for(var f=["Event","Exception","Metric","PageView","Trace","Dependency"];f.length;)b("track"+f.pop());if(b("setAuthenticatedUserContext"),b("clearAuthenticatedUserContext"),b("startTrackEvent"),b("stopTrackEvent"),b("startTrackPage"),b("stopTrackPage"),b("flush"),!a.disableExceptionTracking){f="onerror",b("_"+f);var g=e[f];e[f]=function(a,b,d,e,h){var i=g&&g(a,b,d,e,h);return!0!==i&&c["_"+f](a,b,d,e,h),i}}return c
|
||||
}({
|
||||
instrumentationKey: "6ad37ae0-c4ab-4739-925c-1e2773c31f17"
|
||||
});
|
||||
|
||||
// prettier-ignore
|
||||
if (window.location.hostname !== 'localhost') {
|
||||
window.appInsights=appInsights,appInsights.queue&&0===appInsights.queue.length&&appInsights.trackPageView(null, null, {urlReferrer: document.referrer});
|
||||
}
|
||||
@@ -67,10 +67,10 @@ h1 a {
|
||||
content: counter(steps);
|
||||
position: absolute;
|
||||
left: -20px;
|
||||
bottom: -20px;
|
||||
bottom: -3px;
|
||||
font-size: 220px;
|
||||
line-height: 188px;
|
||||
color: #f3f2f1;
|
||||
color: #eaeaea;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
|
||||
20
azure-pipelines.pr.yml
Normal file
20
azure-pipelines.pr.yml
Normal file
@@ -0,0 +1,20 @@
|
||||
# Node.js
|
||||
# Build a general Node.js project with npm.
|
||||
# Add steps that analyze code, save build artifacts, deploy, and more:
|
||||
# https://docs.microsoft.com/azure/devops/pipelines/languages/javascript
|
||||
|
||||
trigger: none
|
||||
|
||||
pool:
|
||||
vmImage: 'Ubuntu-16.04'
|
||||
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: '10.x'
|
||||
displayName: 'Install Node.js'
|
||||
|
||||
- script: |
|
||||
npm install
|
||||
npm run build
|
||||
displayName: 'npm install, build'
|
||||
33
azure-pipelines.yml
Normal file
33
azure-pipelines.yml
Normal file
@@ -0,0 +1,33 @@
|
||||
# Node.js
|
||||
# Build a general Node.js project with npm.
|
||||
# Add steps that analyze code, save build artifacts, deploy, and more:
|
||||
# https://docs.microsoft.com/azure/devops/pipelines/languages/javascript
|
||||
|
||||
pr: none
|
||||
|
||||
trigger:
|
||||
- master
|
||||
|
||||
pool:
|
||||
vmImage: 'Ubuntu-16.04'
|
||||
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: '10.x'
|
||||
displayName: 'Install Node.js'
|
||||
|
||||
- script: |
|
||||
git config user.email "kchau@microsoft.com"
|
||||
git config user.name "Ken Chau"
|
||||
git remote set-url origin https://kenotron:$(git.pat)@github.com/Microsoft/frontend-bootcamp.git
|
||||
git checkout master
|
||||
git pull
|
||||
npm install
|
||||
git checkout -b build_$(Build.BuildId)
|
||||
npm run build
|
||||
git add .
|
||||
git commit -m "adding docs"
|
||||
git subtree split --prefix docs -b temp_$(Build.BuildId)
|
||||
git push origin temp_$(Build.BuildId):refs/heads/gh-pages --force
|
||||
displayName: 'npm install, build and push docs to gh-pages'
|
||||
18
index.html
18
index.html
@@ -4,16 +4,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="stylesheet" href="https://static2.sharepointonline.com/files/fabric/office-ui-fabric-core/9.6.1/css/fabric.min.css" />
|
||||
<link rel="stylesheet" href="./assets/shared.css" />
|
||||
<script type="text/javascript">
|
||||
// prettier-ignore
|
||||
var appInsights=window.appInsights||function(a){
|
||||
function b(a){c[a]=function(){var b=arguments;c.queue.push(function(){c[a].apply(c,b)})}}var c={config:a},d=document,e=window;setTimeout(function(){var b=d.createElement("script");b.src=a.url||"https://az416426.vo.msecnd.net/scripts/a/ai.0.js",d.getElementsByTagName("script")[0].parentNode.appendChild(b)});try{c.cookie=d.cookie}catch(a){}c.queue=[];for(var f=["Event","Exception","Metric","PageView","Trace","Dependency"];f.length;)b("track"+f.pop());if(b("setAuthenticatedUserContext"),b("clearAuthenticatedUserContext"),b("startTrackEvent"),b("stopTrackEvent"),b("startTrackPage"),b("stopTrackPage"),b("flush"),!a.disableExceptionTracking){f="onerror",b("_"+f);var g=e[f];e[f]=function(a,b,d,e,h){var i=g&&g(a,b,d,e,h);return!0!==i&&c["_"+f](a,b,d,e,h),i}}return c
|
||||
}({
|
||||
instrumentationKey: "6ad37ae0-c4ab-4739-925c-1e2773c31f17"
|
||||
});
|
||||
// prettier-ignore
|
||||
window.appInsights=appInsights,appInsights.queue&&0===appInsights.queue.length&&appInsights.trackPageView();
|
||||
</script>
|
||||
<title>Microsoft Days in the Web - Welcome</title>
|
||||
</head>
|
||||
<body class="ms-Fabric">
|
||||
<div class="Container">
|
||||
@@ -76,7 +67,7 @@
|
||||
</li>
|
||||
<li class="Tile Tile--numbered">
|
||||
<div class="Tile-link">
|
||||
UI-Driven State
|
||||
Types & <br />UI-Driven State
|
||||
<div class="Tile-links">
|
||||
<a target="_blank" href="./step1-07/demo/">demo</a> | <a target="_blank" href="./step1-07/exercise/">exercise</a> |
|
||||
<a target="_blank" href="./step1-07/final/">final</a>
|
||||
@@ -93,7 +84,7 @@
|
||||
</li>
|
||||
<li class="Tile Tile--numbered">
|
||||
<div class="Tile-link">
|
||||
Typescript Basics
|
||||
TypeScript Basics
|
||||
<div class="Tile-links">
|
||||
<a target="_blank" href="./step2-01/demo/">demo</a> | <a target="_blank" href="./step2-01/exercise/">exercise</a>
|
||||
</div>
|
||||
@@ -171,8 +162,9 @@
|
||||
<h2>Playground</h2>
|
||||
Build your app here!
|
||||
</li>
|
||||
<li class="Tile Tile--unnumbered"><a target="_blank" href="/playground/" class="Tile-link">Playground</a></li>
|
||||
<li class="Tile Tile--unnumbered"><a target="_blank" href="./playground/" class="Tile-link">Playground</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<script src="./assets/scripts.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
"build": "rimraf docs && webpack --progress --mode production",
|
||||
"start:server": "nodemon -w server server/index.js",
|
||||
"start": "run-p start:server start:client",
|
||||
"static": "live-server --host=localhost"
|
||||
"static": "live-server --host=localhost",
|
||||
"deploy-ghpages": "git push origin :gh-pages && git subtree push --prefix docs origin gh-pages"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
## HTML Demo
|
||||
|
||||
The [HTML demo page](http://localhost:8080/step1-00/html-demo/html-demo.html) is a large collection of HTML elements that you will come across during development. The full list of elements can be found on [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element).
|
||||
The [HTML demo page](http://localhost:8080/step1-01/html-demo/html-demo.html) is a large collection of HTML elements that you will come across during development. The full list of elements can be found on [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element).
|
||||
|
||||
To learn more about each element, click on the elements below.
|
||||
|
||||
|
||||
@@ -13,7 +13,18 @@
|
||||
<body>
|
||||
<section>
|
||||
<h2><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element#Document_metadata">Document Meta Data</a></h2>
|
||||
<p>head, title, link, style</p>
|
||||
<pre>
|
||||
<head>
|
||||
<title>Intro to HTML</title>
|
||||
<link rel="stylesheet" href="./style.css" />
|
||||
|
||||
<style>
|
||||
hr {
|
||||
margin: 40px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
</pre>
|
||||
</section>
|
||||
<hr />
|
||||
|
||||
|
||||
@@ -15,5 +15,6 @@
|
||||
</ul>
|
||||
<div id="markdownReadme"></div>
|
||||
</div>
|
||||
<script src="../assets/scripts.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="../css-demo/css-demo-finished.css" />
|
||||
<link rel="stylesheet" href="../css-demo/css-demo-final.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
|
||||
@@ -4,7 +4,7 @@ Every website, application, form or component starts with markup. The HTML will
|
||||
|
||||
## Demo
|
||||
|
||||
In this exercise we will scaffold out some HTML for out Todo app, and add some basic styling to it.
|
||||
In this exercise we will scaffold out some HTML for our Todo app, then add some basic styling to it.
|
||||
|
||||
### Page scaffold
|
||||
|
||||
@@ -18,7 +18,7 @@ In this exercise we will scaffold out some HTML for out Todo app, and add some b
|
||||
|
||||
1. The DOCTYPE tells the browser that this file is written in modern HTML.
|
||||
2. The HTML tag wraps the entire page, and is the page root. Nothing is placed outside of those tags. Attributes can be set on HTML
|
||||
3. Head will contain all of the page's meta data, in this case a link to our css file
|
||||
3. Head will contain all of the page's meta data, in this case a link to our CSS file
|
||||
4. Body is where all of the visible content should be placed.
|
||||
|
||||
### Content Sectioning
|
||||
@@ -41,7 +41,7 @@ As we saw in the previous demo, HTML elements can be used to describe different
|
||||
|
||||
### Updating the header
|
||||
|
||||
The header of our page is where most of the action is going to happen. First, lets give our page a title, adding 'TODO' to our `h1`. Then we can add an input and button to our `addTodo` div.
|
||||
The header of our page is where most of the action is going to happen. First, lets give our app a name, adding 'TODO' to our `h1`. Then we can add an input and button to our `addTodo` div.
|
||||
|
||||
```html
|
||||
<input class="textfield" placeholder="add todo" /> <button class="submit">Add</button>
|
||||
@@ -61,7 +61,7 @@ The navigation for this application is quite simple. We want users to be able to
|
||||
|
||||
### Adding styles
|
||||
|
||||
Now that we've got the top of our application scaffolded, we can add some our styles in the head.
|
||||
Now that we've got the top of our application scaffolded, we can add some styles in the head.
|
||||
|
||||
```html
|
||||
<head>
|
||||
@@ -73,7 +73,7 @@ Now that we've got the top of our application scaffolded, we can add some our st
|
||||
|
||||
It looks like the selected button isn't getting any special styles. Let's dig in and see why that is.
|
||||
|
||||
Open up the browser inspector and target our 'all' button. You'll notice that the blue style is present on the list, but it is being overriden by the `border: none` above it. This is a situation where specificity is winning out over the cascade.
|
||||
Open up the browser inspector and target our 'all' button. You'll notice that the blue style is present on the list, but it is being overridden by the `border: none` above it. This is a situation where specificity is winning out over the cascade.
|
||||
|
||||
> **Cascade** states that if two selectors are equal, the lowest one on the page wins
|
||||
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head></head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<h1></h1>
|
||||
<div class="addTodo"></div>
|
||||
<nav></nav>
|
||||
</header>
|
||||
<main class="filter"></main>
|
||||
<footer></footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="../assets/shared.css" />
|
||||
<link rel="stylesheet" href="https://static2.sharepointonline.com/files/fabric/office-ui-fabric-core/9.6.1/css/fabric.min.css" />
|
||||
</head>
|
||||
|
||||
<body class="ms-Fabric">
|
||||
<div class="Container">
|
||||
<ul class="Tiles">
|
||||
<li class="Tile"><a href="./demo/index.html" class="Tile-link">Demo Start</a></li>
|
||||
<li class="Tile"><a href="./exercise/index.html" class="Tile-link">Exercise Start</a></li>
|
||||
<li class="Tile"><a href="./final/index.html" class="Tile-link">Final</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,6 +1,6 @@
|
||||
# Javascript Demo
|
||||
# JavaScript Demo
|
||||
|
||||
Now that we a UI that looks like a todo app, we need to add functionality to make it **function** like a todo app. In this example we are going to use raw Javascript explicitly modify our application as we interact with it. This will be in stark contrast to the implicit approach we will take when we do this with React in the next exercise.
|
||||
Now that we a UI that looks like a todo app, we need to add functionality to make it **function** like a todo app. In this example we are going to use raw JavaScript explicitly modify our application as we interact with it. This will be in stark contrast to the implicit approach we will take when we do this with React in the next exercise.
|
||||
|
||||
> Keep an eye on how often user actions directly modify the HTML on the page. You'll see this number drop to zero when we start using React.
|
||||
|
||||
@@ -8,13 +8,14 @@ Now that we a UI that looks like a todo app, we need to add functionality to mak
|
||||
|
||||
This demo starts off with a few elements already in place. Let's walk through what's already here.
|
||||
|
||||
- **clearInput()** - This is a generic, reusable function that takes in a `selector` paramater, finds the first matching element, and sets the element's value to an empty string. This direct modification is called a side effect.
|
||||
- **clearInput()** - This is a generic, reusable function that takes in a `selector` parameter, finds the first matching element, and sets the element's value to an empty string. This direct modification is called a side effect.
|
||||
- **getTodoText()** - This is a quick helper function that returns the value inside of our textfield. Notice how some functions return values and how you can set that return to a variable.
|
||||
- **addTodo()** - This is the primary logic of our todo app. Here's how the lines break down.
|
||||
1. `todo` is set to equal the first todo item
|
||||
2. `newTodo` is a clone of todo. Passing true means it is a deep clone, so we get the todo's children as well. Cloning does not duplicate the DOM node. We'll need to insert it in step 4
|
||||
> Note that this approach is very fragile, as it requires a todo node to always be present on the page
|
||||
3. We set the innerText of the `<span class='title'>` to the value returned from getTodoText
|
||||
> Note that if we left off the `()` we'd actully be assiging innerText to the 'function' instead of the function return
|
||||
> Note that if we left off the `()` we'd actually be assigning innerText to the 'function' instead of the function return
|
||||
4. Insert our new todo into the todo's parent (the `ul`), before our reference todo. [insertBefore](https://developer.mozilla.org/en-US/docs/Web/API/Node/insertBefore)
|
||||
- **filter()** - This function takes in a `filterName` string, and a `button` which is a reference to the clicked button.
|
||||
1. Remove any `selected` class names
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
### Write updateRemaining function
|
||||
|
||||
1. Get a reference to the span with the `remaining` class, and store it in a variable
|
||||
2. Use [querySelectAll](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll) to get all of the todos.
|
||||
2. Use [querySelectorAll](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll) to get all of the todos.
|
||||
3. Set the `innerText` of the remaining span to the length of those todos.
|
||||
4. Add updateRemaining() to addTodo
|
||||
|
||||
### Write a clearCompleted function
|
||||
|
||||
1. Get a reference to all of the todos with [querySelectAll](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll)
|
||||
1. Get a reference to all of the todos with [querySelectorAll](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll)
|
||||
2. Use a `for (let todo of todos)` loop to iterate over each todo
|
||||
3. Inside the for loop write an `if` statement to test if the `input` inside of the todo has a checked value of true
|
||||
> Hint: you can use querySelector on any HTML node to find child elements within
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
|
||||
function updateRemaining() {
|
||||
const remaining = document.querySelector('.remaining');
|
||||
const todos = document.querySelectorAll('.todo');
|
||||
const todos = document.querySelectorAll('.todo').length;
|
||||
remaining.innerText = todos;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="../assets/shared.css" />
|
||||
<link rel="stylesheet" href="https://static2.sharepointonline.com/files/fabric/office-ui-fabric-core/9.6.1/css/fabric.min.css" />
|
||||
</head>
|
||||
|
||||
<body class="ms-Fabric">
|
||||
<div class="Container">
|
||||
<ul class="Tiles">
|
||||
<li class="Tile"><a href="./demo/index.html" class="Tile-link">Demo Start</a></li>
|
||||
<li class="Tile"><a href="./exercise/index.html" class="Tile-link">Exercise Start</a></li>
|
||||
<li class="Tile"><a href="./final/index.html" class="Tile-link">Final</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -26,10 +26,10 @@ export class App extends React.Component {
|
||||
}
|
||||
```
|
||||
|
||||
- **import React from 'react';** - This is how we [import modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) in Javascript. This line creates a variable in this file called `React` that is equal to the default `export` of the `react` npm module.
|
||||
- **import React from 'react';** - This is how we [import modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) in JavaScript. This line creates a variable in this file called `React` that is equal to the default `export` of the `react` npm module.
|
||||
- **export class App** - Just like react exports code, our App component is nothing more than an exported "App" class. This allows us to import the class into other files
|
||||
- **extends React.Component** - A JavaScript class is similar to other programming languages (it's a collection of methods and properties). Classes can also be extended, so when we create a React component class, we always extend the base React.Component class. Note that this `Component` class is coming from the `React` variable imported up top.
|
||||
> Note that `<any, any>` is necessary for Typescript which we will touch on later
|
||||
> Note that `<any, any>` is necessary for TypeScript which we will touch on later
|
||||
- **render()** - One of the methods defined by React.Component is the `render()` method. This is a function that defines the HTML the component is going to render.
|
||||
- **return** - Remember that functions can return values in addition to side effects, and this component is no different.
|
||||
- **Inside of the return?** It's HTML! Actually, it's JSX, but with very few exceptions you can treat it like HTML. A few key differences:
|
||||
@@ -63,7 +63,7 @@ React solves this by allowing each control to specify its own data store, called
|
||||
|
||||
#### Adding State
|
||||
|
||||
JavaScipt uses a `constructor` method to instantiate each copy of a class. So for class based controls, this is where we specify state.
|
||||
JavaScript uses a `constructor` method to instantiate each copy of a class. So for class based controls, this is where we specify state.
|
||||
|
||||
```js
|
||||
constructor(props) {
|
||||
@@ -138,7 +138,7 @@ return (
|
||||
);
|
||||
```
|
||||
|
||||
> Note the capitalization of Counter. HTML might not be case sensative, but JSX is! A common practice is to use the capitalized versions of HTML elements to name their JSX counterpart. Button, Select, Label, Form etc.
|
||||
> Note the capitalization of Counter. HTML might not be case sensitive, but JSX is! A common practice is to use the capitalized versions of HTML elements to name their JSX counterpart. Button, Select, Label, Form etc.
|
||||
|
||||
### Exploring Component Props
|
||||
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="../assets/shared.css" />
|
||||
<link rel="stylesheet" href="https://static2.sharepointonline.com/files/fabric/office-ui-fabric-core/9.6.1/css/fabric.min.css" />
|
||||
</head>
|
||||
|
||||
<body class="ms-Fabric">
|
||||
<div class="Container">
|
||||
<ul class="Tiles">
|
||||
<li class="Tile"><a href="./demo/index.html" class="Tile-link">Demo Start</a></li>
|
||||
<li class="Tile"><a href="./final/index.html" class="Tile-link">Final</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,15 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="../assets/shared.css" />
|
||||
<link rel="stylesheet" href="https://static2.sharepointonline.com/files/fabric/office-ui-fabric-core/9.6.1/css/fabric.min.css" />
|
||||
</head>
|
||||
|
||||
<body class="ms-Fabric">
|
||||
<div class="Container">
|
||||
<ul class="Tiles">
|
||||
<li class="Tile"><a href="./demo/index.html" class="Tile-link">Demo Start</a></li>
|
||||
<li class="Tile"><a href="./final/index.html" class="Tile-link">Final</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,10 +1,10 @@
|
||||
# Creating a State Driven UI
|
||||
|
||||
In React data travels two directions, top down in the form of state propegating throughout controls, and bottom up, as interacting with the UI flows back up to modify the state. When writing an application it's often helpful to think of these two directions as separate parts of the development process.
|
||||
In React data travels two directions, top down in the form of state propagating throughout controls, and bottom up, as interacting with the UI flows back up to modify the state. When writing an application it's often helpful to think of these two directions as separate parts of the development process.
|
||||
|
||||
## demo
|
||||
|
||||
[Step #3 of Thinking in React](https://reactjs.org/docs/thinking-in-react.html) suggests finding the "minimal set of mutable state" that your application requires. So in this demo we are going to add that "minimal state" to our application and drive our UI off of that data. With that done the next step will be to create ways to modify that state, which will in turn cascade down through our UI. This [reconcilation](https://reactjs.org/docs/reconciliation.html) process, figuring out what in your UI needs to change based on changing state, is what React excels in.
|
||||
[Step #3 of Thinking in React](https://reactjs.org/docs/thinking-in-react.html) suggests finding the "minimal set of mutable state" that your application requires. So in this demo we are going to add that "minimal state" to our application and drive our UI off of that data. With that done the next step will be to create ways to modify that state, which will in turn cascade down through our UI. This [reconciliation](https://reactjs.org/docs/reconciliation.html) process, figuring out what in your UI needs to change based on changing state, is what React excels in.
|
||||
|
||||
### Adding State to App
|
||||
|
||||
@@ -71,15 +71,15 @@ I've already pulled out our props into `filter` and `todos` variables, and writt
|
||||
}
|
||||
```
|
||||
|
||||
- A JavaScript map takes in an array and transforms it into a new array
|
||||
- We use the `id` from the `filterTodos` array as the [list key](https://reactjs.org/docs/lists-and-keys.html) to help React track each item as state changes.
|
||||
- The key is not actually passed into the component, so we pass the key in as `id` as well. This will help us out later.
|
||||
- Lastly we use the `id` to grab the todo from our `todos` object, then use the [spread operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) to pass through the todo's `label` and `completed` values.
|
||||
> This spread opperator is the same as saying `label={todos[id].label} completed={todos[id].completed}`. Pretty obvious why spread is so handy!
|
||||
- **map**: A JavaScript map takes in an array (filteredTodos) and transforms it into a new array (our rendered TodoListItems)
|
||||
- **key**: We use the `id` from the `filterTodos` array as the [list key](https://reactjs.org/docs/lists-and-keys.html) to help React track each item as state changes.
|
||||
- **id**: The key is not actually passed into the component, so we pass the key in as `id` as well. This will help us out later.
|
||||
- **todos[id]**: Lastly we use the `id` to grab the todo from our `todos` object, then use the [spread operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) to pass through the todo's `label` and `completed` values.
|
||||
> This spread operator is the same as saying `label={todos[id].label} completed={todos[id].completed}`. Pretty obvious why spread is so handy!
|
||||
|
||||
### State Driven and Stateful Header
|
||||
|
||||
Within the header we've got a situation where we not only want to pass `filter` state down to it, but we also want to maintain state within the control. Fortunatly, this is no problem at all for React. First off let's deal with the incoming state.
|
||||
Within the header we've got a situation where we not only want to pass `filter` state down to it, but we also want to maintain state within the control. Fortunately, this is no problem at all for React. First off let's deal with the incoming state.
|
||||
|
||||
#### Conditional ClassNames
|
||||
|
||||
@@ -87,15 +87,15 @@ In CSS based styling, visual states are applied by adding and removing classes.
|
||||
|
||||
```jsx
|
||||
<nav className="filter">
|
||||
<button className={filter == 'all' ? 'completed' : ''}>all</button>
|
||||
<button className={filter == 'active' ? 'completed' : ''}>active</button>
|
||||
<button className={filter == 'completed' ? 'completed' : ''}>completed</button>
|
||||
<button className={filter == 'all' ? 'selected' : ''}>all</button>
|
||||
<button className={filter == 'active' ? 'selected' : ''}>active</button>
|
||||
<button className={filter == 'completed' ? 'selected' : ''}>completed</button>
|
||||
</nav>
|
||||
```
|
||||
|
||||
> Terniary operators are very popular in React code as each expression could be a string for a className, or even a JSX element.
|
||||
> Ternary operators are very popular in React code as each expression could be a string for a className, or even a JSX element.
|
||||
|
||||
#### Creating a Controled Input
|
||||
#### Creating a Controlled Input
|
||||
|
||||
In tradition HTML forms users interact with the form, and on submit, those values are captured and transmitted. Those are called **uncontrolled inputs**. A **controlled input** is one whos value is defined by state, and interaction with that input updates state with each keystroke. This round trip process might sound inefficient, but in reality it has little to no impact, and it enables some advanced form functionality.
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
export const TodoFooter = (props: any) => {
|
||||
const itemCount = Object.keys(props.todos).filter(id => !props.todos[id].completed).length;
|
||||
// const itemCount = Object.keys(props.todos).filter(id => !props.todos[id].completed).length;
|
||||
return (
|
||||
<footer>
|
||||
<span>4 items left</span>
|
||||
|
||||
@@ -5,9 +5,9 @@ export class TodoList extends React.Component<any, any> {
|
||||
render() {
|
||||
const { filter, todos } = this.props;
|
||||
|
||||
const filteredTodos = Object.keys(todos).filter(id => {
|
||||
return filter === 'all' || (filter === 'completed' && todos[id].completed) || (filter === 'active' && !todos[id].completed);
|
||||
});
|
||||
// const filteredTodos = Object.keys(todos).filter(id => {
|
||||
// return filter === 'all' || (filter === 'completed' && todos[id].completed) || (filter === 'active' && !todos[id].completed);
|
||||
// });
|
||||
return (
|
||||
<ul className="todos">
|
||||
<TodoListItem />
|
||||
|
||||
@@ -17,9 +17,9 @@ export class TodoHeader extends React.Component<any, any> {
|
||||
<button className="submit">Add</button>
|
||||
</div>
|
||||
<nav className="filter">
|
||||
<button className={filter == 'all' ? 'completed' : ''}>all</button>
|
||||
<button className={filter == 'active' ? 'completed' : ''}>active</button>
|
||||
<button className={filter == 'completed' ? 'completed' : ''}>completed</button>
|
||||
<button className={filter == 'all' ? 'selected' : ''}>all</button>
|
||||
<button className={filter == 'active' ? 'selected' : ''}>active</button>
|
||||
<button className={filter == 'completed' ? 'selected' : ''}>completed</button>
|
||||
</nav>
|
||||
</header>
|
||||
);
|
||||
|
||||
@@ -5,7 +5,7 @@ export class TodoHeader extends React.Component<any, any> {
|
||||
super(props);
|
||||
this.state = { labelInput: '' };
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
const { filter } = this.props;
|
||||
|
||||
@@ -17,9 +17,9 @@ export class TodoHeader extends React.Component<any, any> {
|
||||
<button className="submit">Add</button>
|
||||
</div>
|
||||
<nav className="filter">
|
||||
<button className={filter == 'all' ? 'completed' : ''}>all</button>
|
||||
<button className={filter == 'active' ? 'completed' : ''}>active</button>
|
||||
<button className={filter == 'completed' ? 'completed' : ''}>completed</button>
|
||||
<button className={filter == 'all' ? 'selected' : ''}>all</button>
|
||||
<button className={filter == 'active' ? 'selected' : ''}>active</button>
|
||||
<button className={filter == 'completed' ? 'selected' : ''}>completed</button>
|
||||
</nav>
|
||||
</header>
|
||||
);
|
||||
|
||||
@@ -6,21 +6,21 @@ Now that we have a UI that is purely driven by the state of our app, we need to
|
||||
|
||||
This is our core 'business logic' and handles everything our basic 'CRUD' operations of "Create, Read, Update, Delete". We don't have time to walk through writing all of those functions, but you can see that they are already provided in the demo's `TodoApp` and passed into our components.
|
||||
|
||||
## Intro to Typescript
|
||||
## Intro to TypeScript
|
||||
|
||||
Taking a look at our components in `TodoApp` you can see that our list of props is not just getting longer, but is getting much more complex! We're passing through functions with various signatures, complex `todos` objects as well as filter strings which are always one of three values.
|
||||
|
||||
As applications grow, it becomes increasing difficult to remember what each function does, or what each todo contains. Also, as JavaScript is a loosly type language, if I wanted to change the value of `todos` to an array inside my `TodoList`, javascript wouldn't care. But if `TodoListItems` was expecting an object, our application would break.
|
||||
As applications grow, it becomes increasing difficult to remember what each function does, or what each todo contains. Also, as JavaScript is a loosely type language, if I wanted to change the value of `todos` to an array inside my `TodoList`, JavaScript wouldn't care. But if `TodoListItems` was expecting an object, our application would break.
|
||||
|
||||
It is because of these two reasons that the entire industry is shifting to writing applications that are strongly typed, and are using Typescript to accomplish that.
|
||||
It is because of these two reasons that the entire industry is shifting to writing applications that are strongly typed, and are using TypeScript to accomplish that.
|
||||
|
||||
As [their website](https://www.typescriptlang.org/) state:
|
||||
|
||||
> Typescript is a superset of JavaScript that compiles to plain JavaScript
|
||||
> TypeScript is a superset of JavaScript that compiles to plain JavaScript
|
||||
|
||||
If you've ever used [Sass](https://sass-lang.com/) you are familiar with this concept. In the same say that all valid CSS is valid Sass, all valid JavaScript is valid Typescript. That's why most of this project has been writting in `ts` and `tsx` files instead of `js` and `jsx` files.
|
||||
If you've ever used [Sass](https://sass-lang.com/) you are familiar with this concept. In the same say that all valid CSS is valid Sass, all valid JavaScript is valid TypeScript. That's why most of this project has been writing in `ts` and `tsx` files instead of `js` and `jsx` files.
|
||||
|
||||
Let's dive into the demo and see how Typescript can help us better understand our component props, and guard against future regressions.
|
||||
Let's dive into the demo and see how TypeScript can help us better understand our component props, and guard against future regressions.
|
||||
|
||||
## Demo
|
||||
|
||||
@@ -80,7 +80,7 @@ Now try going back to `TodoApp` and changing the `filter` attribute in `TodoList
|
||||
|
||||
#### Complete Type
|
||||
|
||||
The `complete` props isn't data, but rather a function. Fortunatly, Typescript can handle function types just as well as data.
|
||||
The `complete` props isn't data, but rather a function. Fortunately, TypeScript can handle function types just as well as data.
|
||||
|
||||
```tsx
|
||||
interface TodoListProps {
|
||||
@@ -115,7 +115,7 @@ Now that our interface is complete, try changing the word 'all' in `filter === a
|
||||
|
||||
### Abstracting types
|
||||
|
||||
Most of our components are going to need to add types for `todos` and `filter`, so it's a good thing that Typescript allows us to abstract those. I've already written up and exported those shared types in the file `TodoApp.types.ts`, so we just need to import them and pull them into our interface.
|
||||
Most of our components are going to need to add types for `todos` and `filter`, so it's a good thing that TypeScript allows us to abstract those. I've already written up and exported those shared types in the file `TodoApp.types.ts`, so we just need to import them and pull them into our interface.
|
||||
|
||||
```tsx
|
||||
import { FilterTypes, Todos } from '../TodoApp.types';
|
||||
@@ -129,7 +129,7 @@ interface TodoListProps {
|
||||
|
||||
### Updating TodoApp
|
||||
|
||||
Our `TodoApp` doesn't take any props, but it does have state. We can use Typescript to define that as well.
|
||||
Our `TodoApp` doesn't take any props, but it does have state. We can use TypeScript to define that as well.
|
||||
|
||||
I've already imported `Todos`, and `FilterTypes` into the `TodoApp`, so we just need to add them to our class. We can even skip the 'interface', if we want to, and add them directly to the class.
|
||||
|
||||
@@ -160,8 +160,12 @@ const { label, completed, complete, id } = this.props;
|
||||
|
||||
And then use the input's `onChange` event to fire our `complete` callback. We can see in the signature that we expect and `id` of type string, so we'll pass our `id` prop in.
|
||||
|
||||
> A [callback function](https://developer.mozilla.org/en-US/docs/Glossary/Callback_function) is a function passed into a component as a prop
|
||||
|
||||
```tsx
|
||||
<input type="checkbox" checked={completed} onChange={() => complete(id)} />
|
||||
```
|
||||
|
||||
> Note that the function param and prop name just happen to be the same. This isn't required.
|
||||
|
||||
Now that our todos are firing the `onChange` callback, give them a click and take look at how the app response. Since our footer text is driven off of the number of unchecked todos, the footer will automatically update to reflect the new state.
|
||||
|
||||
@@ -10,7 +10,24 @@ export class TodoApp extends React.Component<any, any> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
todos: {},
|
||||
todos: {
|
||||
'04': {
|
||||
label: 'Todo 4',
|
||||
completed: true
|
||||
},
|
||||
'03': {
|
||||
label: 'Todo 3',
|
||||
completed: false
|
||||
},
|
||||
'02': {
|
||||
label: 'Todo 2',
|
||||
completed: false
|
||||
},
|
||||
'01': {
|
||||
label: 'Todo 1',
|
||||
completed: false
|
||||
}
|
||||
},
|
||||
filter: 'all'
|
||||
};
|
||||
}
|
||||
|
||||
@@ -10,7 +10,24 @@ export class TodoApp extends React.Component<any, { todos: Todos; filter: Filter
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
todos: {},
|
||||
todos: {
|
||||
'04': {
|
||||
label: 'Todo 4',
|
||||
completed: true
|
||||
},
|
||||
'03': {
|
||||
label: 'Todo 3',
|
||||
completed: false
|
||||
},
|
||||
'02': {
|
||||
label: 'Todo 2',
|
||||
completed: false
|
||||
},
|
||||
'01': {
|
||||
label: 'Todo 1',
|
||||
completed: false
|
||||
}
|
||||
},
|
||||
filter: 'all'
|
||||
};
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ export class TodoHeader extends React.Component<any, any> {
|
||||
}
|
||||
|
||||
_onFilter = evt => {
|
||||
this.props.setFilter(evt.target.textContet);
|
||||
this.props.setFilter(evt.target.innerText);
|
||||
};
|
||||
|
||||
_onChange = evt => {
|
||||
|
||||
@@ -44,7 +44,7 @@ export class TodoHeader extends React.Component<TodoHeaderProps, TodoHeaderState
|
||||
}
|
||||
|
||||
_onFilter = evt => {
|
||||
this.props.setFilter(evt.target.textContet);
|
||||
this.props.setFilter(evt.target.innerText);
|
||||
};
|
||||
|
||||
_onChange = evt => {
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="../assets/shared.css" />
|
||||
<link rel="stylesheet" href="https://static2.sharepointonline.com/files/fabric/office-ui-fabric-core/9.6.1/css/fabric.min.css" />
|
||||
</head>
|
||||
|
||||
<body class="ms-Fabric">
|
||||
<div class="Container">
|
||||
<ul class="Tiles">
|
||||
<li class="Tile"><a href="./demo/index.html" class="Tile-link">Demo Start</a></li>
|
||||
<li class="Tile"><a href="./final/index.html" class="Tile-link">Final</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,8 +1,8 @@
|
||||
# Step 2.1: Introduction to Typescript
|
||||
# Step 2.1: Introduction to TypeScript
|
||||
|
||||
[Lessons](../) | [Exercise](./exercise/) | [Demo](./demo/)
|
||||
|
||||
In this step, we'll cover enough of the Typescript concepts to be productive with the React & Redux frameworks.
|
||||
In this step, we'll cover enough of the TypeScript concepts to be productive with the React & Redux frameworks.
|
||||
|
||||
Topics in this step will include:
|
||||
|
||||
@@ -22,13 +22,13 @@ The most important ones to know about are:
|
||||
- commonjs - Node.js's standard to support modules
|
||||
- synchronous
|
||||
- require() function, can be dynamically called in the course of a program
|
||||
- ESM (Ecmascript module) - language level support
|
||||
- ESM (ECMAScript module) - language level support
|
||||
- statically analyzable and synchronous
|
||||
- dynamic and asynchronous support via `import()` that returns a promise
|
||||
|
||||
## Typescript Types
|
||||
## TypeScript Types
|
||||
|
||||
Refer to the `demo/src` for some examples of some of the types avaible in TS that benefits a React developer.
|
||||
Refer to the `demo/src` for some examples of some of the types available in TS that benefits a React developer.
|
||||
|
||||
## Spread Syntax
|
||||
|
||||
@@ -162,7 +162,7 @@ Create inside `index.ts`:
|
||||
|
||||
Inside `stack.ts`, create a generic class for a `Stack<T>` complete with a typed `pop()` and `push()` methods
|
||||
|
||||
> Hint: the Javascript array already has `push()` and `pop()` implemented for you. That can be your backing store.
|
||||
> Hint: the JavaScript array already has `push()` and `pop()` implemented for you. That can be your backing store.
|
||||
|
||||
Be sure to use the provided `log()` to show the functionality of `Stack<T>`
|
||||
|
||||
|
||||
@@ -8,5 +8,6 @@
|
||||
<div id="app">
|
||||
Nothing to show here, just look at your console window for output. Hit F12 to open console window.
|
||||
</div>
|
||||
<script src="../../assets/scripts.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Interesting Typescript Topics
|
||||
// Interesting TypeScript Topics
|
||||
|
||||
// types
|
||||
import './types';
|
||||
|
||||
@@ -8,5 +8,6 @@
|
||||
<div id="app">
|
||||
Nothing to show here, just look at your console window for output. Hit F12 to open console window.
|
||||
</div>
|
||||
<script src="../../assets/scripts.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -6,5 +6,6 @@
|
||||
<body class="ms-Fabric">
|
||||
<div id="markdownReadme"></div>
|
||||
<div id="app"></div>
|
||||
<script src="../../assets/scripts.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -6,5 +6,6 @@
|
||||
<body class="ms-Fabric">
|
||||
<div id="markdownReadme"></div>
|
||||
<div id="app"></div>
|
||||
<script src="../../assets/scripts.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -6,5 +6,6 @@
|
||||
<body class="ms-Fabric">
|
||||
<div id="markdownReadme"></div>
|
||||
<div id="app"></div>
|
||||
<script src="../../assets/scripts.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -6,5 +6,6 @@
|
||||
<body class="ms-Fabric">
|
||||
<div id="markdownReadme"></div>
|
||||
<div id="app"></div>
|
||||
<script src="../../assets/scripts.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[Lessons](../) | [Exercise](./exercise/) | [Demo](./demo/)
|
||||
|
||||
Testing Typescript code with jest. jest is a test framework made by Facebook and is very popular in the React and the wider JS ecosystem. We will work on implementing simple unit tests here in this exercise.
|
||||
Testing TypeScript code with jest. jest is a test framework made by Facebook and is very popular in the React and the wider JS ecosystem. We will work on implementing simple unit tests here in this exercise.
|
||||
|
||||
https://jestjs.io/
|
||||
|
||||
@@ -43,7 +43,7 @@ describe('Foo Component Tests', () => {
|
||||
});
|
||||
```
|
||||
|
||||
`mount` does a full mount of the component. You can use the `enzyme` wrapper to simulate clicks, etc:
|
||||
`mount` does a full mount of the component. You can use the `enzyme` wrapper to simulate clicks, etc.:
|
||||
|
||||
```ts
|
||||
wrapper.find('button').simulate('click');
|
||||
|
||||
@@ -10,5 +10,6 @@
|
||||
<pre>npm test</pre>
|
||||
in the command line.
|
||||
</div>
|
||||
<script src="../../assets/scripts.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -10,5 +10,6 @@
|
||||
<pre>npm test</pre>
|
||||
in the command line.
|
||||
</div>
|
||||
<script src="../../assets/scripts.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -10,5 +10,6 @@
|
||||
<pre>npm test</pre>
|
||||
in the command line.
|
||||
</div>
|
||||
<script src="../../assets/scripts.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -10,5 +10,6 @@
|
||||
<pre>npm test</pre>
|
||||
in the command line.
|
||||
</div>
|
||||
<script src="../../assets/scripts.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -10,5 +10,6 @@
|
||||
<pre>npm test</pre>
|
||||
in the command line.
|
||||
</div>
|
||||
<script src="../../assets/scripts.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -10,5 +10,6 @@
|
||||
<pre>npm test</pre>
|
||||
in the command line.
|
||||
</div>
|
||||
<script src="../../assets/scripts.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -6,5 +6,6 @@
|
||||
<body class="ms-Fabric">
|
||||
<div id="markdownReadme"></div>
|
||||
<div id="app"></div>
|
||||
<script src="../../assets/scripts.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -6,5 +6,6 @@
|
||||
<body class="ms-Fabric">
|
||||
<div id="markdownReadme"></div>
|
||||
<div id="app"></div>
|
||||
<script src="../../assets/scripts.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -6,5 +6,6 @@
|
||||
<body class="ms-Fabric">
|
||||
<div id="markdownReadme"></div>
|
||||
<div id="app"></div>
|
||||
<script src="../../assets/scripts.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -6,5 +6,6 @@
|
||||
<body class="ms-Fabric">
|
||||
<div id="markdownReadme"></div>
|
||||
<div id="app"></div>
|
||||
<script src="../../assets/scripts.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
[Lessons](../) | [Exercise](./exercise/) | [Demo](./demo/)
|
||||
|
||||
(First off, this doesn't work with the live site on github.io. Clone this repo to try this step out)
|
||||
|
||||
Redux Thunk middleware for actions with service calls. The documentation is here:
|
||||
|
||||
https://github.com/reduxjs/redux-thunk
|
||||
|
||||
@@ -6,5 +6,6 @@
|
||||
<body class="ms-Fabric">
|
||||
<div id="markdownReadme"></div>
|
||||
<div id="app"></div>
|
||||
<script src="../../assets/scripts.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -6,5 +6,6 @@
|
||||
<body class="ms-Fabric">
|
||||
<div id="markdownReadme"></div>
|
||||
<div id="app"></div>
|
||||
<script src="../../assets/scripts.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user