I'm learning how to use service worker, my problem is that when clicking the button to fetch() some file when the page is loaded for first time didn't fire any fetch event, but after manually refresh the page then clicking the button again, the fetch event get fired as expected
Please guide how to fix this
thanks
Chrome: 60
service-worker.js
self.addEventListener('install', event => {
event.waitUntil(
caches.open('XXX')
.then(cache => {
cache.allAll([ ... ])
})
)
})
self.addEventListener('activate', event => {
console.log('ready')
})
self.addEventListener('fetch', event => {
console.log('fetch')
})
index.html
<body>
<button id="btn" class="button">Click</button>
<script>
(() => {
document.getElementById('btn').addEventListener('click', () => {
fetch(' ... ')
.then(res => {
return res.json()
})
.then(res => {
console.log(res)
})
})
if (!('serviceWorker' in navigator)) {
console.log('Service Worker is not supported')
return
}
navigator.serviceWorker.register('/service-worker.js')
.then((registration) => {
console.log('Sevice Worker has been registered')
})
})()
</script>
</body>
you probably need a self.clients.claim() to activate the service-worker asap.
self.addEventListener('activate', function(event) {
event.waitUntil(self.clients.claim());
});
see: What is the use of `self.Clients.claim()`
Related
I am working on a HTML5 PWA and am looking to have a "Network First, then Cache" layout so that when a user accesses the PWA it pulls down the latest version but if they are offline it uses cache.
How would i need to amend the below code for that please?
const assets = [
"/",
"/index.html",
"/about.html",
"/assets/css/main.css",
"/images/logo.png",
"/images/logo.svg",
]
self.addEventListener("install", installEvent => {
installEvent.waitUntil(
caches.open(staticCacheName).then(cache => {
cache.addAll(assets)
})
)
})
self.addEventListener("fetch", fetchEvent => {
fetchEvent.respondWith(
caches.match(fetchEvent.request).then(res => {
return res || fetch(fetchEvent.request)
})
)
})
The cache is created, but once the PWA is saved to homepage it always uses Cache.
You're looking for the "network, then cache" pattern from the Offline Cookbook:
self.addEventListener('fetch', (event) => {
event.respondWith(async function() {
try {
return await fetch(event.request);
} catch (err) {
return caches.match(event.request);
}
}());
});
So I am making a ToDo app but so far I can just create a task, or delete it. Right now I am trying to make another feature where I can edit the specific task by clicking the edit button and then it will change the task into an input area where i can edit the task name. Can someone help me with this? How it looks right now is displayed below.
My Code right now is below:
import React, { Component } from 'react';
import axios from "axios";
export default class TaskInput extends Component {
constructor(props) {
super(props)
this.state = {
task: " ",
allTasks: [],
strikeThrough: {textDecoration:""}
}
}
changeHandler = (event) => {
console.log(event.target.value)
this.setState({
task: event.target.value,
})
}
handleStrikethrough = (completed, id) => {
// !completed ? this.setState({strikeThrough:{textDecoration: "line-through"}}) : this.setState({strikeThrough:{textDecoration:""}})
// if (!completed) {
// console.log("not completed", !completed)
// this.setState({strikeThrough:{textDecoration: "line-through"}});
// axios.put("/api/task", {
// completed: !completed
// }, id).then(response => console.log(response))
// } else {
// this.setState({strikeThrough:{textDecoration:""}})
// axios.put("/api/task", {
// completed: !completed
// }, id).then(response => console.log(response))
// }
}
handleDelete = (taskId) => {
axios.delete("/api/task/" + taskId).then(data => {
console.log("You deleted the task with an id of ", data)
})
window.location.reload();
}
handleTaskEdit = () => {
console.log("edit button worked")
}
submitHandler = (event) => {
event.preventDefault() //to prevent page refresh
console.log()
fetch("/api/task", {
method: "POST",
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify(this.state),
})
.then(res => res.json())
.then(data => console.log(data))
.catch(err => console.log(err))
this.setState({
task: ""
})
window.location.reload()
}
componentDidMount() {
console.log("component did mount")
const self = this;
axios.get("/api/tasks").then(function (data) {
self.setState({
allTasks: data.data
})
// console.log(self.state.allTasks[0].task)
})
}
render() {
const {strikeThrough, task, allTasks} = this.state; //destructuring the state
return (
<div>
<form onSubmit={this.submitHandler} >
<label style={{ margin: "5px 0px" }}>Create a Task:</label>
<input value={this.state.task} onChange={this.changeHandler} style={{ width: "100%" }}></input>
<input style={{ padding: "5px", marginTop: "5px" }} type="submit"></input>
</form>
<hr></hr>
<br></br>
<ul>
{this.state.allTasks.map(task => (
<li style={strikeThrough} onClick={()=>this.handleStrikethrough(task.completed, task.id)} className="tasks">{task.task}
<button onClick = {() => this.handleDelete(task.id)}>x</button>
<button onClick={this.handleTaskEdit}>edit</button>
</li>
)
)}
</ul>
</div>
)
}
}
You could set task ID on its corresponding Edit button, then when clicking Edit button get the task using ID and sending that task to an edit component.
First of all handleTaskEdit, here you set task name to the task property and set ID of editable task:
handleTaskEdit = id =>
this.setState({ task: this.state.allTasks.find(el => el.id === id).task })
secondly, create two new methods, createTask and updateTask:
createTask = () => {
fetch("/api/task", {
method: "POST",
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({task: this.state.task}),
})
.then(res => res.json())
.then(data => this.setState({
task: '',
allTasks: [...this.state.allTasks, data]}))
.catch(err => console.log(err))
}
updateTask = () => {
fetch("/api/task", {
method: "PATCH",
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({task: this.state.task, id: this.state.editableTaskId}),
})
.then(res => res.json())
.then(data => this.setState({
task: '',
editableTaskId: null,
allTasks: this.state.allTasks.map(el =>
el.id === data.id ? data : el)})) // Or take new name and id from state
.catch(err => console.log(err))
}
and finally you need to update submitHandler and handleDelete:
submitHandler = () => {
if (this.state.editableTaskId) {
this.updateTask();
} else {
this.createTask()
}
}
handleDelete = (taskId) => {
axios.delete("/api/task/" + taskId).then(data => {
this.setState({allTasks: this.state.allTasks.filter(el =>
el.id !== data.id
)})
})
}
Here's the approach:
Have a state variable called editTaskID and keep the default value as null. On the edit button set the functionality of handleTaskEdit in such a way that it sets the editTaskID to that particular task ID on which edit button was clicked.
In the map function where you are rendering the list items for tasks, add a condition such as:
{this.state.allTasks.map(task =>
(
<li style={strikeThrough}
onClick={()=>this.handleStrikethrough(task.completed, task.id)}
className="tasks">
{
this.editTaskID
?<input
value={this.state.editTaskName}
/*supposing editTaskName to be state variable that stores
the edit textfield content.*/
onChange={this.changeEditHandler} style={{ width: "80%" }}>
</input>
:task.task
}
<button onClick = {() => this.handleDelete(task.id)}>x</button>
<button onClick={this.handleTaskEdit}>edit</button>
</li>
)
)
}
This will now check the condition whether the editTaskID has been set to null or not while rendering. In case if it is null, all your tasks will come as a plain text else it will come in form of a text box. You can also add the value to the edit task input field with the help of allTasks[editTaskID].
On the handleTaskEdit function of the edit button, make sure to set the allTasks[editTaskID] to the value editTaskName and also to set the state variable editTaskID to null.
Call the necessary backend endpoint to reflect the changes in your database as well.
I hope it helps. Thanks.
I'm trying to populate a table with some information pulled from my database through an API based on year. I'm using React Router and would like to keep my sidebar with the links to different years, but dynamically change the table that is the main focus of this page.
I can get the Table to render with the first year(2019, since this is the default link), but that's only if it's outside the <Switch>. This also causes the problem that it doesn't change when the link changes.
class YearlyTable extends React.Component {
state = {
yearlyTable: [],
isLoading: false,
}
componentDidMount() {
this.setState({ isLoading: true });
axios.get(
`http://localhost/yearTable/${this.props.match.params.yearId}${this.props.location.search}`,
{ withCredentials: true }
).then(res => {
const yearlyTable = res.data;
this.setState({ yearlyTable, isLoading: false });
}).catch((error) => {
console.log(error);
});
}
render() {
// isLoading component
// Check what API returns
console.log(this.state.yearlyBonds);
return (
// Removed for simplicity
// This returns a table
{this.state.yearlyTable && <ListTable title={this.state.yearlyTable.Title} data={this.state.yearlyTable.Bonds} />}
// This does not
<Switch>
<Route exact path={`/yearly_table/${this.props.match.params.yearId}${this.props.location.search}`} render={() => this.state.yearlyTable && <ListTable title={this.state.yearlyTable.Title} data={this.state.yearlyTable} />} />
</Switch>
// Sidebar removed for simplicity
);
}
}
export default withRouter(YearlyTable);
The outcome I'm wanting to achieve is that it renders the first table, but when you press one of the links, then it changes out the table with the new contents.
This is happening because you are using componentDidMount. This is called only for the first render, not after that.
You can do something like
class YearlyTable extends React.Component {
state = {
yearlyTable: [],
isLoading: false,
}
componentDidMount() {
this.setState({ isLoading: true });
axios.get(
`http://localhost/yearTable/${this.props.match.params.yearId}${this.props.location.search}`,
{ withCredentials: true }
).then(res => {
const yearlyTable = res.data;
this.setState({ yearlyTable, isLoading: false });
}).catch((error) => {
console.log(error);
});
}
updateData(){
axios.get(
`http://localhost/yearTable/newYearID${this.props.location.search}`,
{ withCredentials: true }
).then(res => {
const yearlyTable = res.data;
this.setState({ yearlyTable, isLoading: false });
}).catch((error) => {
console.log(error);
});
}
render() {
// isLoading component
// Check what API returns
console.log(this.state.yearlyBonds);
return (
// Removed for simplicity
// This returns a table
{this.state.yearlyTable && <ListTable title={this.state.yearlyTable.Title} data={this.state.yearlyTable.Bonds} />}
<Link onClick={this.updateData.bind(this, clickedItemYear)}>Some Item</Link>
);
}
}
export default withRouter(YearlyTable);
You can use and preventDefault or stopPropogation of the event. Better make a function call , so that it is called again whenever there is some user action.
For some reason the second then command in my js file line 12 seems to be nonconductant.
Why is that ? Thanks
Edit: The questionn should have been : For some reason the second console.log does not give an output.
This question has been sucessfully answered by the commentators. Thank you
codepen
document.getElementById('btn').addEventListener('click', btnPressed);
function btnPressed() {
fetch("https://randomuser.me/api")
.then(function(res) {
console.log(res);
return res.json();
})
.then(function(data) {
console.log(data);
})
.catch(err => console.log('Error,with message:', err.statusText))
}
<button id=btn>Button</button>
Both console.log functions are working... Please run the below code.
document.getElementById('btn').addEventListener('click', btnPressed);
function btnPressed() {
fetch("https://randomuser.me/api")
.then(function(res) {
console.log("...................1st console Start");
console.log(res);
console.log("...................1st console End");
return res.json();
})
.then(function(data) {
console.log("...................2nd console Start");
console.log(data);
console.log("...................2nd console End");
})
.catch(err => console.log('Error,with message:', err.statusText))
}
<button id=btn>Button</button>
When a promise is called in the client side. What is the best way to catch/display error without console.log()? Example as follows
.then(result => {
})
.catch(err => {
console.log(err);
});
Maybe you are looking for:
console.error = () => { throw new Error(FEATURES_ERROR); };
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error