I've created a notes app. On the front page it has categories and notes. You can click on the categories and get all of the notes under that category. I just added a button that lets you delete the category and all of its notes and then navigate back to the front page. It looks like this:
Button:
<IonRow>
<IonButton onClick={deletecat} >
Delete Category
</IonButton>
</IonRow>
Here is the deletecat function:
const deletecat = () => {
const trashcategory = ({category}) => {
try {
fetch(`https://fakeurl.com/delcat/${category}/`, {
method: "DELETE",
mode: "cors",
headers: {
"Content-Type": "application/json",
},
})
} catch (error) {
console.log("error time!", error);
return false;
}
};
trashcategory({category})
router.push('/notes')
}
When I click on my button I get this error:
NotFoundError: Node.removeChild: The node to be removed is not a child of this node
This issue was actually addressed on SO before (React Error: NotFoundError: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node), but their specific solution was for jquery. But I think the concept is the same:
This issue occurs when you:
1. Render something using React
2. Then, you manipulate DOM rendered by React with external script
3. Now on the next render cycle(re-render), React doesn't find the DOM node it rendered previously as its already modified/removed by external script
How do I resolve this? Is there any way I can re-render the DOM in a way where it doesn't try and look for what was rendered previously? How else might I get around this?
edit: This is the front page:
useEffect(() => {
getcategories({username})
getnotes({username})
console.log("USEEFFECTTIME")
},[]);
const getcategories = ({ username }) => {
try {
fetch(`https://fakeurl.com/getcategories`, {
method: "POST",
mode: "cors",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({username}),
})
.then(res => res.json())
.then(data => {
setCategorydata(data);
setLoading(false);
})
} catch (error) {
console.log("error time!", error);
return false;
}
};
console.log('before get categories')
const getnotes = async ({ username }) => {
try {
//await fetch(`/getnotes`, {
await fetch(`https://fakeurl.com/getnotes`, {
method: "POST",
mode: "cors",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({username}),
})
.then(res => res.json())
.then(data2 => {
setNotedata(data2);
setLoading2(false)
})
} catch (error) {
console.log("error time!", error);
return false;
}
};
const categories = categorydata.categories
const notes = notedata.notes
<IonSlides id="slider" options={{ slidesPerView: "auto", zoom: true, grabCursor: true }} className={ `${ styles.categorySlider } ion-padding-bottom` }>
{ categories.map((category, index) => {
const noteCount = notes.filter(n => n.note_category === category.id).length;
return (
<IonSlide key={ `categorySlide_${ index }`}>
<IonCol className="ion-text-left">
<IonCard routerLink={`/categorypage/${category.id}`}>
<IonCardHeader className="ion-no-padding" >
<div className={ styles.slideCount }>
<h6>{ noteCount } { noteCount === 1 ? "note" : "notes" } </h6>
</div>
<div className={ styles.slideHeader }>
<h4 style={{color:"black"}}>{ category.category }</h4>
</div>
</IonCardHeader>
<IonCardContent>
<div className={ styles.categoryColor } style={{ borderBottom: `2px solid ${ category.color }` }}></div>
</IonCardContent>
</IonCard>
</IonCol>
</IonSlide>
);
})}
</IonSlides>
<IonGrid className={ styles.bottomContainer }>
<IonRow>
<IonCol size="12" className="ion-padding-start">
<IonCardSubtitle className={ styles.heading }>
Recent Notes
</IonCardSubtitle>
</IonCol>
</IonRow>
<div className={ styles.recentNotes }>
{ notes.slice(0).reverse().map((note, index) => {
return (
<IonRow key={ `note_${ index }` } className="animate__animated animate__faster" id={ `noteRow_${ note.id }` }>
<IonCol size="12">
<Link to={`/Update/${note.id}`}>
<h2>{note.note_name}</h2>
</Link>
</IonCol>
</IonRow>
);
})}
</div>
</IonGrid>
Per your comments above, to make the changes reflect on both the front page and the component page, you call the data from the API at the parent component level and then pass the data and setData down as props to the component page.
If the front page and the component page are siblings under a router, then you can pull the data in the router component and share as props or implement the useContext hook to share the data.
Updates like changing note contents and deleting categories should happen on the front-end and then be updated to the back-end via fetch requests asynchronously.
The problem is coming from the asynchronous delay between updates from the API competing with synchronous unmounts with calls to the router.push method. By relying on local state data, you should get rid of your problem.
Related
I'm making a shopping app and I want to have a button that sends a PATCH request to my db to update the value cart_status from false to true. I have the button working, but I think my syntax is off on the PATCH function. Also, if anyone is extra helpful, my img tags are not rendering the image and I don't know why (they are external image URLs if that helps).
Here is my code:
import React, { useState } from "react";
function ItemCard({ item }) {
const [addToCart, setAddToCart] = useState(true)
const handleAddToCart = () => {
setAddToCart(addToCart => !addToCart)
fetch(`/items/${item.id}`, {
method: 'PATCH',
headers: {
"Content-Type": 'application/json',
},
body: JSON.stringify(item.cart_status)
})
.then(resp => resp.json())
.then(item(setAddToCart))
}
return (
<div className="card">
<img src={item.image_url} alt={item.item_name} />
<h4>{item.item_name}</h4>
<p>Price: ${item.price}</p>
<p>* {item.description} *</p>
{addToCart ? (
<button className="primary" onClick={handleAddToCart}>Add To Cart</button>
) : (
<button onClick={handleAddToCart}>Remove From Cart</button>
)}
</div>
);
}
export default ItemCard;
The results of clicking the "Add To Cart" button changes the state of the button, but does not update in the db. I also get this error message in the terminal:
Started PATCH "/items/1" for 127.0.0.1 at 2022-11-08 15:30:14 -0600
Processing by ItemsController#update as */*
Parameters: {"_json"=>false, "id"=>"1"}
Item Load (0.2ms) SELECT "items".* FROM "items" WHERE "items"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
↳ app/controllers/items_controller.rb:24:in `update'
Unpermitted parameters: :_json, :id
[active_model_serializers] Rendered ItemSerializer with ActiveModelSerializers::Adapter::Attributes (0.38ms)
Completed 202 Accepted in 3ms (Views: 0.7ms | ActiveRecord: 0.2ms | Allocations: 997)
Because true/false are boolean values, you can send a patch request to the rails attribute and use the bang operator "!" to flip the false on the backend.
import React, { useState } from "react";
function ItemCard({ item }) {
// console.log(item)
const [addToCart, setAddToCart] = useState(item.cart_status)
const handleAddToCart = () => {
setAddToCart(addToCart => !addToCart)
fetch(`/items/${item.id}`, {
method: 'PATCH',
headers: {
"Content-Type": 'application/json',
},
body: JSON.stringify({ cart_status: !item.cart_status })
})
.then(resp => resp.json())
.then(data => console.log(data))
window.location.reload(false)
}
return (
<div className="card">
<img src={item.img_url} alt={item.item_name} />
<h4>{item.item_name}</h4>
<p>Price: ${item.price}</p>
<p>* {item.description} *</p>
{addToCart ? (
<button onClick={handleAddToCart}>Remove From Cart</button>
) : (
<button className="primary" onClick={handleAddToCart}>Add To Cart</button>
)}
</div>
);
}
export default ItemCard;
So what I am basically doing is, I have an API of call activities and its own details.
I have to archive a call and in that API of calls, each call has a field called "is_archived"
I need to be able to update the API using a click of a button to archive a call. (So basically change the field of "is_archived" from "false" to "true" once the button is clicked)
And once that call has been archived, it shouldnt render nor be displayed on the application anymore.
I'm getting a "Failed to load resource: the server responded with a status of 400 (Bad Request)" with my code and I'm sure I'm doing something wrong, I just cant spot it.
Thank you!
Here is my code so far:
App.jsx
import React, { Component} from 'react';
import { ActivityFeed } from './components/activity-feed/activity-feed.component.jsx';
import Header from './Header.jsx';
class App extends Component {
constructor() {
super();
this.state = {
calls: [],
showMessage: false,
is_archived: false
};
}
componentDidMount() {
fetch('https://aircall-job.herokuapp.com/activities')
.then(response => response.json())
.then(activities => this.setState({ calls: activities }))
document.getElementById("reset").disabled = true;
}
handleArchive = event => {
this.setState({calls: []});
this.setState({ showMessage: true });
document.getElementById("archive").disabled = true;
document.getElementById("reset").disabled = false;
};
handleReset = event => {
this.componentDidMount();
this.setState({ showMessage: false });
document.getElementById("reset").disabled = true;
document.getElementById("archive").disabled = false;
};
render() {
const { calls, showMessage } = this.state;
console.log(calls);
return (
<div className='App'>
<Header/>
<ActivityFeed calls={calls} />
<button type="button" className="archive-btn" id="archive"
onClick={this.handleArchive}>Archive All Calls</button>
{showMessage && <p>All calls have been archived</p>}
<button type="button" className="reset-btn" id="reset"
onClick={this.handleReset}>Reset Archived Calls</button>
</div>
);
};
}
export default App;
Activity.component.jsx
import React from 'react';
import './activity-detail.styles.css';
import missed from '../../resources/images/missed.svg';
import answered from '../../resources/images/answered.svg';
import voicemail from '../../resources/images/voicemail.svg';
function formatDate(date) {
var localDate = new Date(date);
return localDate.toDateString().split(' ').slice(1).join(' ');
}
function formatTime(time) {
var localTime = new Date(time);
return localTime.toLocaleTimeString().replace(/(.*)\D\d+/, '$1');;
}
function callType(type) {
if (type === "missed") {
return <img src={missed} alt="missed" className="call-icon"/>
}
else if (type === "answered") {
return <img src= {answered} alt="answered" className="call-icon"/>
}
else
return <img src= {voicemail} alt="voicemail" className="call-icon"/>
}
function archiveCall(id) {
fetch(`https://aircall-job.herokuapp.com/activities/${id}`, {
mode: 'no-cors',
method: "POST",
headers: {
'Accept' : 'application/json',
"Content-Type": "application/json"
},
body: JSON.stringify({
is_archived: true
}),
})
}
export const Activity = props => (
<div className='activity-container'>
<p> Date {formatDate(props.call.created_at)} </p>
<p> Time {formatTime(props.call.created_at)} </p>
<p> From {props.call.from} </p>
<p> To {props.call.to} </p>
<p> Via {props.call.via} </p>
<p> Call type {callType(props.call.call_type)} </p>
<button type="button" className="archive-call" id="archive-call"
onClick={archiveCall(props.call.id)}
>Archive call</button>
</div>
);
ActivityFeed.component.jsx
import React from 'react';
import { Activity } from '../activity-detail/activity-detail.component';
import './activity-feed.styles.css';
export const ActivityFeed = props => (
<div className='activity-feed'>
{props.calls.map(calls => (
<Activity key={calls.id} call={calls}/>
))}
</div>
);
For some reason, I'm not sure why, but if you have no-cors mode set on your fetch POST request, the content-type on your request gets changed to text/plain.
Fetch API - Content-Type is sent as text/plain when it's set to application/json
I was able to figure this out by duplicating your request in a sandbox, right-clicking your request in Chrome's network tab, and choosing "Copy as cURL". Then import it into Postman so I could replicate the exact request. There I could see it was converted into a plain-text content instead of a JSON body as intended.
Of course you can also see these things in the requests inside the network tab, but sometimes putting it inside Postman makes it stand out more clearly.
So the solution is to simply omit the "no-cors" option and your request works fine in React.
fetch(`https://aircall-job.herokuapp.com/activities/${id}`, {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify({
is_archived: true
})
});
https://codesandbox.io/s/cranky-khayyam-qm2rz?file=/src/App.js
I'm trying to do a little react app pulling some uniswap crypto data for my own UI just for fun, I've grabbed some data with a graphql query and I'm trying to render it out on the condition that its loaded which I get from a ternary operator in my functional component.
when I try this in multiple combinations, I just get the error that allTokenData.map is not a function
I have included my component below and I have notated where my mapping function is trying to pull data from the array I get back from graphql, since I'm getting data I'm sure I'm just mixing something up with the mapping syntax :/
here is a snippet of the data I'm grabbing for reference logged in the console, any help is appreciated
function CoinData(props) {
//fetch whichever coin we want to add
const NEWCOIN_QUERY = gql`
query tokens($tokenAddress: Bytes!) {
tokens(where: { id: $tokenAddress }) {
derivedETH
totalLiquidity
}
}
`;
const { loading: ethLoading, data: ethPriceData } = useQuery(ETH_PRICE_QUERY);
const { loading: allLoading, data: allTokenData } = useQuery(QUERY_ALL_TOKENS);
const { loading: coinLoading, data: coindata } = useQuery(NEWCOIN_QUERY, {
variables: {
tokenAddress: props.parentState.newcoins!== '' ? props.parentState.newcoins.toString() : '0x6b175474e89094c44da98b954eedeac495271d0f',
},
});
const coinPriceInEth = coindata && coindata.tokens[0].derivedETH;
const coinTotalLiquidity = coindata && coindata.tokens[0].totalLiquidity;
const ethPriceInUSD = ethPriceData && ethPriceData.bundles[0].ethPrice;
console.log(props.parentState.newcoins)
return (
<div>
<div>
coin price:{" "}
{ethLoading || coinLoading
? "Loading token data..."
: "$" +
// parse responses as floats and fix to 2 decimals
(parseFloat(coinPriceInEth) * parseFloat(ethPriceInUSD)).toFixed(2)}
</div>
<div>
Coin total liquidity:{" "}
{coinLoading ? "Loading token data...": parseFloat(coinTotalLiquidity).toFixed(0)}
</div>
<div>
</div>
<div>
//////////////////////////////////////////----map function////////////////////////////
{allLoading ? "Loading token data...":
<div>
{allTokenData.map((token, index) => (
<p key={index}> {token.id} SYN: {token.symbol}</p>
))}
</div>
}
//////////////////////////////////////////----map function////////////////////////////
</div>
</div>
);
}
maybe allTokenData.tokens.map.
It is because allTokenData is an object.
const {tokens} = allTokenData
{tokens.map((token, index) => (
<p key={index}> {token.id} SYN: {token.symbol}</p>
))}
I'm trying to call an API to show information on my website in React.js, the API needs a token to be read, but I don't know what I have to do to generate the token, since my application doesn't need any register or login. It's like a catalog of products and you can personalize them. I don't understand very well this because I'm new and I'm learning all by myself, so I'm sorry if this is confusing, feel free to ask anything, I'll try to answer :)
Here's the code I have until now:
export class Main extends React.Component {
constructor(props) {
super(props);
this.state = {
models: [],
isLoaded: false
};
}
componentDidMount() {
fetch(myUrlAPI)
.then(res => res.json())
.then(json => {
this.setState({
isLoaded: true,
models: json
});
});
}
render() {
const { isLoaded, models } = this.state;
if (!isLoaded) {
return <div>Loading...</div>;
} else {
return (
<div>
<ul>{models.map(model => <li>{model.name}</li>)};</ul>
<a href="/sofa">
<div className="Parcelas">
<img
src="../../img/image-card-1#2x.png"
className="ParcImage"
alt="sofa"
/>
<h1>Sofa hipnos</h1>
<h2>
1,200<span>€</span>
</h2>
<p className="Features">
w 20.5 x 15.5 x h 19.5 (cm)<br />Pele
</p>
<button className="Botao">
<p className="MostraDepois">See Details</p>
<span>+</span>
</button>
<img
src="../../img/points.svg"
className="Decoration"
alt="points"
/>
</div>
</a>
</div>
);
}
}
}
In the <ul> tag I was just trying to test the results from the json.
Also, everytime I use .map to make an array, I get this error (TypeError: models.map is not a function) or similar, can you tell me why?
Try this way and for token you need to confirm with the backend developer as how and what you should send.
let token = '1234567';
fetch(myUrlAPI, {
method: "GET",
headers: {
"authorization-key": token
}
}).then(res => res.json()
).then(function(json) {
this.setState({
isLoaded: true,
models: json
});
}, function(error) {
console.log(error)
})
I am trying to use fetch to get json data from my back end and then put it on a array, and show it on the screen, for now on the console log.
I am trying to store the information I get back in a array called data which I initialized in getinistate, and then put json data in it while the fetch call is done. For now The error I am reciving is that console.log is basically empty.
Here is the code.
<body>
<div id="reactBinding"></div>
<script type="text/babel">
var Heading = React.createClass({
getInitialState: function() {
return {
data: [],
amount : 1000
};
},
handleChange: function(event){
this.setState({amount : event.target.value});
},
loadCommentsFromServer: function() {
var value = {
method : 'GET' ,
headers : {
'Accept': 'application/json',
'contentType' : 'application/x-www-form-urlencoded',
},
body : ({
amount : this.state.amount
})
};
fetch('http://localhost:3000/getIOT', value)
.then((response) => response.json())
.then((responseData) =>{
responseData : this.state.data
})
.catch(function(err){
console.log(err);
});
},
showTable : function(){
console.log(data);
},
render : function(){
var amount = this.state.amount;
return(
<div className="container">
<div className="row">
<div classname="col-xs-4 col-xs-offset-4">
<div className="text-center">
<h1>{this.props.name}</h1>
<h2> {amount} </h2>
<input type="text" value={amount} onChange={this.handleChange} />
<button onClick={this.showTable}>Show Table</button>
<button onClick={this.loadCommentsFromServer}> Submit </button>
</div>
</div>
</div>
</div>
);
}
});
ReactDOM.render(
<div>
<Heading
name="React JS"
>
</Heading>
</div>
, document.getElementById('reactBinding'));
</script>
</body>
So again, what I want to do is get the information from fetch, put it in the variable called data array and then when someone clicks showTable, it should console.log the array out. Totally new to react so need a bit of handholding since this is the first time I am writing it. If this code is a bit too messy it would be great someone could help show me how to write a simple fetch.
Also if you have time it would be great if someone could explain how can I display the array in a table. in the showTable part.
You need to use the setState to store the data in state variable once you get the response, like this:
fetch('http://localhost:3000/getIOT', value)
.then((response) => response.json())
.then((responseData) =>{
//responseData : this.state.data
this.setState({data: responseData}); // use this line
})
put the console.log in render function, it will print the data once you get the response, like this:
render : function(){
var amount = this.state.amount;
console.log('data', this.state.data);
....
Update:
Check the working Code:
var Heading = React.createClass({
getInitialState: function() {
return {
data: [],
amount : 1000
};
},
handleChange: function(event){
this.setState({amount : event.target.value});
},
loadCommentsFromServer: function() {
var value = {
method : 'GET' ,
headers : {
'Accept': 'application/json',
'contentType' : 'application/x-www-form-urlencoded',
},
body : ({
amount : this.state.amount
})
};
fetch('http://localhost:3000/getIOT', value)
.then((response) => response.json())
.then((responseData) =>{
this.setState({data: responseData});
})
.catch(function(err){
console.log(err);
});
},
showTable : function(){
console.log(this.state.data);
},
render : function(){
var amount = this.state.amount;
console.log('data', this.state.data);
return(
<div className="container">
<div className="row">
<div classname="col-xs-4 col-xs-offset-4">
<div className="text-center">
<h1>{this.props.name}</h1>
<h2> {amount} </h2>
<input type="text" value={amount} onChange={this.handleChange} />
<button onClick={this.showTable}>Show Table</button>
<button onClick={this.loadCommentsFromServer}> Submit </button>
</div>
</div>
</div>
</div>
);
}
});
ReactDOM.render(
<div>
<Heading
name="React JS"
>
</Heading>
</div>
, document.getElementById('reactBinding'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id='reactBinding'></div>