this is my first react app and I don't understand what's wrong.I'm trying to display the page but I'm getting the error in the title twice(on lines 26:5 - where the fetch(url, { is and 39:9 where I have the line setisLoaded(true), setArticles(result.articles);).I changed the code back and forth from App class component to functional component,but no luck(though fetching the data works/worked).I tried enclosing all the code inside the useEffect() method into a return statement only to have all sort of parsing errors that I can't fix.So here's the code:
import React, { Fragment, useState, useEffect } from 'react';
import Title from './components/Title/Title';
import Content from './components/Content/Content';
import 'bootstrap/dist/css/bootstrap.min.css';
import { Card, Navbar, Form, FormControl, Button } from 'react-bootstrap';
const App = (props) => {
const [error, setError] = useState(null);
const [isLoaded, setisLoaded] = useState(false);
const [articles, setArticles] = useState([]);
const [country, setCountry] = useState('gb');
const handleSubmit = (event) => {
event.preventDefault();
changeCountryHandler();
};
const changeCountryHandler = (event) => {
setCountry(event.target.value);
};
useEffect(() => {
let url =
'https://cors-anywhere.herokuapp.com/http://newsapi.org/v2/top-headlines?country='
+
country;
fetch(url, {
method: 'GET',
headers: {
Accept: 'application/json, text/plain, */*',
'Content-type': 'application/json',
'x-api-key': 'myApiKey',
SameSite: 'None',
},
})
.then((res) => res.json())
.then((result) => {
setisLoaded(true), setArticles(result.articles);
}),
(error) => {
setisLoaded(true);
setError(error);
};
});
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <div>Loading...</div>;
} else {
return (
<>
<Navbar className="bg-secondary justify-content-between" variant="dark">
<p>The selected country is:{country}</p>
<Form inline onSubmit={handleSubmit}>
<FormControl
type="text"
placeholder="Search by country"
className=" mr-sm-2"
onChange={changeCountryHandler}
value={country}
/>
<Button type="submit">Submit</Button>
</Form>
</Navbar>
{articles.map((article) => (
<Card
style={{ width: '100%', height: 'auto' }}
key={article.title}
className="mb-5 mt-4 bg-light"
>
<Card.Img
variant="top"
src={article.urlToImage}
className="pr-3 pl-3"
alt=""
/>
<Card.Body>
<Card.Title>
{article.author} | {article.publishedAt}
<Title title={article.title}>{article.title}</Title>
</Card.Title>
Description: {article.description}
<Content content={article.content}>{article.content}</Content>
Read the full article on{' '}
<Card.Link
href={article.url}
target="_blank"
rel="noopener noreferrer"
>
{article.url}
</Card.Link>
</Card.Body>
</Card>
))}
</>
);
}
};
export default App;
Initially I wanted to be able to change the country parameter from the URL in order to fetch the desired content from the API,based on what's submitted from the form(I know I should have a select with options instead of an input type text,but I'm just testing this for now).But at this point,I'm just happy if I'll be able to display any page,since the other responses to this issue(adding a return to the useEffect() arrow function,enclosing statements within parenthesis instead of curly braces) didn't work for me and I don't understand this error.Any idea would be appreciated.
One error is for these:
setisLoaded(true), setArticles(result.articles);
Statements are separated by semicolons, not commas, so the fix is:
setisLoaded(true);
setArticles(result.articles);
One error is a misplaced parentheses, which is causing the error callback to be a separate expression:
}), // <-----
(error) => {
setisLoaded(true);
setError(error);
};
It should instead be:
}, (error) => {
setisLoaded(true);
setError(error);
}); // <----- moved to here
You are missing the dependencies array in the useEffect.
useEffect(() => {
//your code
}, []);
If you add an empty array it will only fetch the data once when your component mounts. If you add any prop or state inside the array it will fetch when the component mounts and also when the prop or state added changes.
Always remember to add the array as a second parameter of the useEffect to avoid that infinite loop.
Related
I'll try to be as brief as I can since my doubt is quite big.
What I'm trying to do: I'm creating a simple app that reads a JSON file stored locally (%APPDATA% folder, but it doesn't matter for now) that contains an array of objects called TCodes:
[
{
"code": "TCODE1",
"description": "TCode 1",
"keywords": "tcode1"
}
]
TCodes are then mapped and showed inside a MUI List in a custom component called TCodesList, placed inside the Home component. Above the TCodesList there is a Toolbar custom component that wraps a input field and an IconButton that opens a MUI Dialog that lets the user create a new TCode and store it in the JSON file.
Reading and writing operations are done with the fs module, I will omit how it is done since it is not relevant. It's enough to know that I do a fetch request to the /api/tcodes endpoint that do different things based on the request verb (GET or POST).
Problem: When I create a new TCode, it is written inside the JSON file, but the TCodesList does not get updated.
Here is the index.js file where the TCodes are read from the file through the useSWR hook:
//imports ...
function useTCodes() {
const fetcher = (...args) => fetch(...args).then((res) => res.json());
const { data, error, isLoading } = useSWR('/api/tcodes', fetcher);
return {
data: data,
isLoading: isLoading,
isError: error
}
}
export default function Home() {
const { data } = useTCodes();
return (
<div>
<Toolbar data={data} />
<div className={styles.tcodes_container}>
<TCodesList data={data}/>
</div>
</div>
);
};
The TCodesList component just maps the data passed as a prop and renders each ListItem:
export default function TCodesList({ data }) {
const filtered = data?.filter((tCode) => {
return tCode.code.toLowerCase().includes(''.toLowerCase());
});
return (
<List>
{
filtered && filtered.map((tCode, index) => (
<div key={index}>
{index != 0 && <Divider />}
<ListItem>
<div>
<ListItemText className={styles.tcodes_list_item_code}>{tCode.code}</ListItemText>
<ListItemText className={styles.tcodes_list_item_description}>{tCode.description}</ListItemText>
</div>
</ListItem>
</div>
))
}
</List>
);
}
Here is the AddDialog component where I send the POST request to write the new TCode in the JSON file:
//imports ...
export default function AddDialog({ open, setOpen }) {
const [tCode, setTCode] = useState('');
const [description, setDescription] = useState('');
const [keywords, setKeywords] = useState('');
const initState = () => {
setTCode('');
setDescription('');
setKeywords('');
}
const closeDialog = () => {
setOpen(false);
}
const addTCode = () => {
fetch('http://localhost:3000/api/tcodes', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
code: tCode,
description: description,
keywords: keywords
})
});
initState();
closeDialog();
}
const handleTCodeChange = (event) => {
setTCode(event.target.value);
}
const handleDescriptionChange = (event) => {
setDescription(event.target.value);
}
const handleKeywordsChange = (event) => {
setKeywords(event.target.value);
}
return (
<Dialog open={open}
onClose={closeDialog}>
<DialogTitle>Add new TCode</DialogTitle>
<DialogContent>
<TextField value={tCode}
onChange={handleTCodeChange}
autoFocus
fullWidth
margin="dense"
label="TCode"
type="text"
variant='outlined' />
<TextField value={description}
onChange={handleDescriptionChange}
fullWidth
margin='dense'
label="Description"
type="text"
variant='outlined' />
<TextField value={keywords}
onChange={handleKeywordsChange}
fullWidth
margin='dense'
label="Keywords"
type="text"
variant='outlined' />
</DialogContent>
<DialogActions>
<Button onClick={closeDialog}>Cancel</Button>
<Button onClick={addTCode}>Add</Button>
</DialogActions>
</Dialog>
);
}
The addTCode function is where the file is updated. In this way I would have expected the SWR package to revalidate the data and update it inside the TCodesList component.
P.S. I'm passing data as props throughout the app. I explored different solutions to better manage the state providing inside the app and to manage the state updating (context, prop drilling, context + dispatchers) but it seemed all a bit of an overkill so I'm sticking with SWR right now since it has a few benefits.
I'm probably missing something obvious since I'm quite new to this. Does anyone have any idea on how to accomplish what explained above?
Thanks in advance for the help.
You need to ask SWR to mutate the data after you make your POST request. You can do that by passing mutate returned by useSWR to TCodesList:
//imports ...
function useTCodes() {
const fetcher = (...args) => fetch(...args).then((res) => res.json());
const { data, error, isLoading, mutate } = useSWR("/api/tcodes", fetcher);
return {
data: data,
isLoading: isLoading,
isError: error,
mutate: mutate,
};
}
export default function Home() {
const { data, mutate } = useTCodes();
return (
<div>
<Toolbar data={data} />
<div className={styles.tcodes_container}>
<TCodesList data={data} mutate={mutate} />
</div>
</div>
);
}
Then pass this same mutate to AddDialog along with data, to use it inside addTCode, like so:
const addTCode = async () => {
const newItem = {
code: tCode,
description: description,
keywords: keywords,
};
await fetch("http://localhost:3000/api/tcodes", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(newItem),
});
// update the local data immediately and revalidate (refetch)
// NOTE: key is not required when using useSWR's mutate as it's pre-bound
mutate([ ...data, newItem ]);
initState();
closeDialog();
};
I'm a new developer.
If you login to our website, JWT will be created.
When I press the button, I have to put it in the API as a backend.
And if the screen is successful, the address in the API must be printed out.
If it fails, 'authentication failure' should be displayed on the screen.
I want to do this. Please help me.
import axios from 'axios';
import React, { useState } from 'react';
import { Button } from '#material-ui/core';
function TestPage() {
const onLogin = () => {
var variables = {
email: email,
password: password,
};
Axios.post('/auth/login', variables).then((res) => {
setCookie('token', res.payload.accessToken);
setCookie('exp', res.payload.accessTokenExpiresIn);
Axios.defaults.headers.common['Authorization'] = `Bearer ${res.payload.accessToken}`;
Axios.get('/user/me').then((res) => {
console.log(res);
});
});
};
return (
<>
<div>
<Button
variant="contained"
color="primary"
style={{ width: '200px' }}
onClick={(e) => customFetch(e)}>
address
</Button>
</div>
{address && <div>{address}</div>}
</>
);
}
export default TestPage;
Generally for any network operation, it's helpful to know when it's in progress, has finished, and/or has an error. Let's set this up:
const [isLoading, setIsLoading] = useState(false)
const [data, setData] = useState(null)
const [error, setError] = useState(null)
// inside your `onLogin` function...
setIsLoading(true);
Axios.post('/auth/login', variables).then((res) => {
setCookie('token', res.payload.accessToken);
setCookie('exp', res.payload.accessTokenExpiresIn);
Axios.defaults.headers.common['Authorization'] = `Bearer ${res.payload.accessToken}`;
// bit messy using the same error state for both but you can always refactor
Axios.get('/user/me').then((res) => {
console.log(res);
setData(res); // not sure where the actual data is with Axios
}).catch(err => setError(err);
}).catch(err => setError(err));
setIsLoading(false);
During your POST, set the state variables accordingly:
Before the post, setIsLoading(true)
On success, setData(response.data) // whatever your payload might be
On error/failure, setError(error)
Now in your component return, you can conditionally render your different states, e.g:
// your component body
if (isLoading) return (
// a loading state
)
if (error) return (
// an error state
// e.g. "Authentication Failure"
)
return (
// your success/ideal state
// e.g:
<>
<div>
<Button
variant="contained"
color="primary"
style={{ width: '200px' }}
onClick={(e) => customFetch(e)}>
address
</Button>
</div>
{address && <div>{address}</div>}
</>
)
Alternatively, you could leverage the variables slightly differently:
return (
<>
<div>
<Button
variant="contained"
color="primary"
style={{ width: '200px' }}
onClick={(e) => customFetch(e)}
disabled={isLoading}>
address
</Button>
</div>
<div>
{isLoading
? 'Checking...'
: error !== null
? 'Something went wrong'
: 'Ready to submit'}
</div>
</>
)
The ternary style can be a bit messy though.
When I use setApi(data.time); in the fetch section I can normally do console.log(api.updated);, but why I can not do just like what I wrote in the code below?
CodeSandbox
import React, { useState, useEffect } from "react";
import "./styles.css";
export default function App() {
const [api, setApi] = useState({});
useEffect(() => {
fetch("https://api.coindesk.com/v1/bpi/currentprice.json")
.then((res) => res.json())
.then((data) => {
setApi(data);
});
}, []);
console.log(api.time.updated);
return (
<div className="App">
<h1>Currency Exchange</h1>
{/* <p>Time: {api.time.updated}</p>
<u>
<li>Code: {api.bpi.USD.code}</li>
<li>Rate: {api.bpi.USD.rate}</li>
</u> */}
</div>
);
}
Before the request is complete api will be an empty object. api.time will then be undefined, and trying to access property updated on that will give rise to your error.
You could use the logical AND && operator to make sure api.time is set.
const [api, setApi] = useState({});
useEffect(() => {
fetch("https://api.coindesk.com/v1/bpi/currentprice.json")
.then((res) => res.json())
.then((data) => {
setApi(data);
});
}, []);
console.log(api.time && api.time.updated);
Your revised code:
import React, { useState, useEffect } from "react";
import "./styles.css";
export default function App() {
const [api, setApi] = useState({});
useEffect(() => {
fetch("https://api.coindesk.com/v1/bpi/currentprice.json")
.then((res) => res.json())
.then((data) => {
setApi(data);
});
}, []);
return (
<div className="App">
<h1>Currency Exchange</h1>
{api.time?.updated && <><p>Time: {api.time.updated}</p>
<u>
<li>Code: {api.bpi.USD.code}</li>
<li>Rate: {api.bpi.USD.rate}</li>
</u></>}
</div>
);
}
Simple way to check and ensure the API resolved with the appropriate data, then show the designated information.
Note that there's a reason why you have .then for your API query - it takes time to come back. Your code was executing before the api state could be filled with the response.
With the logical && operator, there's a simple way to look at it with React and I use it all the time.
If I have a loader component or something I want to show only when the loading variable is true, I can do something like this:
{loading && <Loader />}
The code to the right of the && will ONLY run if the left side is true. Since it's AND, if the first part is false it doesn't matter what the other parts are and they're skipped.
you can use a extra state to check loading data and display it when fetch done:
export default function App() {
//add loading to check api request and data to save result
const [api, setApi] = useState({ loading: false, data: undefined });
useEffect(() => {
setApi({ loading: true });
fetch("https://api.coindesk.com/v1/bpi/currentprice.json")
.then((res) => res.json())
.then((data) => {
setApi({ loading: false, data: data });
});
}, []);
return (
<>
{!api.loading && api.data ? (//check if data is loaded
<div className="App">
<h1>Currency Exchange</h1>
<p>Time: {api.data.time.updated}</p>
<u>
<li>Code: {api.data.bpi.USD.code}</li>
<li>Rate: {api.data.bpi.USD.rate}</li>
</u>
</div>
) : (
<>Loading data...</>//show a message while loading data(or <></> if want not display something)
)}
</>
);
}
how do I get the values from the component that has the input to the component that is actually going to make the post? is that posible? or should I put all in the same component?
this is my Item component:
import React, {useState} from 'react';
import {Col} from 'reactstrap';
export function CustomFieldsItem({item}) {
const [value, setValue] = useState(false);
function handleChange(e) {
setValue(e.target.value);
}
return (
<>
<li className={'list-group-item d-flex border-0'} key={item.id}>
<Col md={2}>
<label>{item.label}</label>
</Col>
<Col md={10}>
<input className="form-control" type="text" value={value || item.value} onChange={handleChange} />
// <-- this is the value I want to pass to my update component when I type on it
// but the save button is in the "update component" (the one under)
</Col>
</li>
</>
);
}
This is my update Component:
import React, {useState, useEffect} from 'react';
import axios from 'axios';
import {post} from '../../common/fetch/fetchOptions';
export function CustomFieldsUpdate({item}) {
const [value, setValue] = useState(false);
const updateCustomField = async ({data}) => {
try {
await axios(
post({
url:'http://localhost:9000/projectcustomfields.json/update/1741?id=party&value=newvalue',
// <-- I want to set the value here, instead of "newvalue" but right now Im happy with just a log of the value from the component above in the console when I click on "Save" button.
// When I save right now I just post hardcoded values (which is what I want to change)
data: data,
method: 'POST',
mode: 'cors',
withCredentials: true,
credentials: 'include',
}),
);
console.log(value);
} catch (e) {
console.log('Error when updating values: ', e);
}
};
return (
<div className={'d-flex justify-content-end mr-4'}>
<button
type={'button'}
className={'btn btn-primary mr-2'}
onClick={updateCustomField}
>
Save
</button>
</div>
);
}
I have another component that list the objects the I want to update, maybe I need to pass the values from this component, maybe can use that?
import React, {useState, useEffect} from 'react';
import {CustomFieldsList} from './customFieldsList';
import {toast} from 'react-toastify';
import {ToastInnerDisplay} from '#learnifier/jslib-utils';
import {CustomFieldsItem} from './customFieldsItem';
export function CustomFieldsContainer({match}) {
const [value, setValue] = useState({
data: null,
loading: true,
error: null,
});
/**
* Initial loading of data.
*/
async function fetchData() {
setValue({...value, error: null, loading: true});
try {
let url = `http://localhost:9000/projectcustomfields.json/list/1741`;
const res = await fetch(url, {
method: 'POST',
mode: 'cors',
withCredentials: true,
credentials: 'include',
});
let data = await res.json();
setValue(prevValue => ({...prevValue, data: data.customFields, loading: false}));
} catch (error) {
toast.error(<ToastInnerDisplay message={error.message} />);
setValue({...value, error, loading: false});
}
}
useEffect(() => {
fetchData();
}, []);
if (value.loading) {
return <div>loading...</div>;
} else if (value.error) {
return <div>ERROR</div>;
} else {
return (
<div className={'section-component'}>
<div className={'col-md-6 col-sm-12'}>
<h2>Custom Fields</h2>
<CustomFieldsList setValue={setValue} list={value.data} />
</div>
</div>
);
// return <ChildComponent data={value.data} />
}
}
I render the components with a component list:
import React from 'react';
import {CustomFieldsItem} from './customFieldsItem';
import {CustomFieldsUpdate} from './customFieldsUpdate';
export function CustomFieldsList({list, setValue, update,
updateCustomField}) {
console.log(list);
console.log(update);
return (
<>
<form>
<ul className={'list-group border-0'}>
{list.map(item => (
<CustomFieldsItem item={item} key={item.id} />
))}
</ul>
<CustomFieldsUpdate updateCustomField={updateCustomField} setValue={setValue}/>
</form>
</>
);
}
So my issue is that I’m basically trying to pass the data that I have fetched from an API on my Home page, which is fetched and stored in the ‘geo’ variable upon pressing the submit button, and pass it onto the Maps page (accessed by pressing the Maps button after a postcode has been submitted), which will then use the latitude and longitude from ’geo’ (geo.result.latitude/longitude) to fetch from another API upon loading and display that data on the page. However, I’m having an issue with understanding how to pass state (or the values within the geo variable) between the two components/pages (Home and Maps) using hooks. I’m reasonably new to React, and very new to Hooks, so my understanding is very basic at the moment. Any help would be much appreciated :)
Home.js
import React, { useState, useEffect, useCallback } from 'react'
import { Link } from 'react-router-dom'
const Home = () => {
const [postCode, setPostcode] = useState({
pCode: ''
})
const [geo, setGeo] = useState([])
const fetchRequest = useCallback((e) => {
e.preventDefault()
fetch(`https://api.postcodes.io/postcodes/${postCode.pCode}`)
.then(res => res.json())
.then(res => setGeo(res))
console.log(geo)
}, [{ ...postCode }])
const handleChange = (e) => {
setPostcode({ ...postCode, pCode: e.target.value })
console.log(postCode)
}
return <section >
<div className='container'>
<form className="form" onSubmit={fetchRequest}>
<input className="input" type="text" placeholder="Text input" onChange={handleChange} />
< button>submit</button>
<Link className='button' to={{
pathname: '/maps'
}}>Map
</Link>
</form>
</div>
</section>
}
export default Home
Maps.js
const Maps = () => {
const [events, setEvents] = useState([])
const fetchRequest = useCallback(() => {
fetch(`https://api.list.co.uk/v1/events?near=${viewport.latitude},${viewport.longitude}/10`, {
headers: {
'Authorization': `Bearer ${API_KEY}`
}
})
.then(res => res.json())
.then(res => setEvents(res))
}, [{ ...viewport }])
useEffect(() => {
fetch(`https://api.list.co.uk/v1/events?near=${geo.result.latitude},${geo.result.longitude}/10`, {
headers: {
'Authorization': `Bearer ${API_KEY}`
}
})
.then(res => res.json())
.then(res => setEvents(res))
return () => console.log('Unmounting component')
}, [])
const [viewport, setViewport] = useState({
width: '100vw',
height: '100vh',
latitude: 51.45523,
longitude: -2.59665,
zoom: 13.5
})
if (events.length === 0) {
return <div>Loading...</div>
}
return <div>
<ReactMapGL mapboxApiAccessToken={TOKEN}
mapStyle="mapbox://styles/dredizzle/ck3owxclr138a1cqnzupab2hc"
{...viewport}
onViewportChange={viewport => {
setViewport(viewport)
}}
onClick={
fetchRequest
}
>
{events.map(event => (
<Popup
key={event.event_id}
latitude={event.schedules[0].place.lat}
longitude={event.schedules[0].place.lng}
>
</Popup>
))}
{/* <Popup latitude={51.45523} longitude={-2.59665}>
<div>event here</div>
</Popup> */}
<GeolocateControl
positionOptions={{ enableHighAccuracy: true }}
trackUserLocation={false}
/>
</ReactMapGL>
</div>
}
export default Maps
In the Maps component, I think you're overwriting the value of events when you repeat this line:
const [events, setEvents] = useState([])
You should just be able to reference this.state.events within the Maps component, and use this.setState() within Maps to add data to the events array from within the component.