How to fix state variable contents not displaying - reactjs

I'm working on building out a small application. Currently I'm stuck at trying to display the response given from my back-end.
I've been using this guide to implement a data fetching hook, and everything works except for trying to display the returned entries in a list.
Here is what my code in the component looks like:
import React, { Fragment, useState, useEffect } from "react";
import axios from 'axios';
function Search() {
const [zone, setZone] = useState('');
const [results, setResults] = useState({entries: []});
const [url, setUrl] = useState(
`http://localhost:4000/entries/view/`
);
useEffect(() => {
const fetchEntries = async () => {
const result = await axios.get(url);
setResults({entries: result.entries});
};
fetchEntries();
}, [url]);
return (
<Fragment>
<input type="text" value={zone} onChange={e => setZone(e.target.value)} />
<button type="btn-primary" onClick={() =>
setUrl(`http://localhost:4000/entries/view/${zone}`)
}>
Search
</button>
<ul>
{results.entries.map(item => (
<li key={item._id}>
<div>{item.entry_description}</div>
</li>
))}
</ul>
</Fragment>
);
}
export default Search;
And here is a sample of what the API response looks like:
{
"entries": [
{
"_id": "5d365d9af8b0625f345a8cea",
"entry_description": "Test ui",
"entry_time": "Now",
"entry_author": "Me",
"entry_zone": "12345",
"__v": 0
},
{
"_id": "5d367e3bbd8b13188c0d638b",
"entry_description": "Test still?",
"entry_time": "Now",
"entry_author": "Me",
"entry_zone": "12345",
"__v": 0
}
]
}
Current error message I'm receiving is:
The above error occurred in the <Search> component:
in Search (created by Context.Consumer)
in Route (at App.js:21)
in div (at App.js:14)
in Router (created by BrowserRouter)
in BrowserRouter (at App.js:13)
in App (at src/index.js:7)
Consider adding an error boundary to your tree to customize error handling behavior.
TypeError: results.entries is undefined
home.component.js:30
TypeError: results.entries is undefined
home.component.js:30
I really appreciate any help figuring out what is causing this.

You seem to be wrapping the api result in an array, whereas you just need to assign it to enteries
useEffect(() => {
const fetchEntries = async () => {
const result = await axios.get(url);
setResults({entries: result.entries});
};
fetchEntries();
}, [url]);

So I've got it working now, although I don't quite understand it. I used this code and changed some of the names to work with my variable names. I think the fix has something to do with the useEffect() dependency in this code including the query variable. In my original code, I combined them into a url, and then didn't also include the zone variable in the dependency.
Here's my final code:
import React, { useState, useEffect } from "react";
import axios from 'axios';
function Search() {
const [data, setData] = useState({ entries: [] });
const [query, setQuery] = useState('Enter zone');
useEffect(() => {
let ignore = false;
async function fetchData() {
const result = await axios('http://localhost:4000/entries/view/' + query);
if (!ignore) setData(result.data);
}
fetchData();
return () => { ignore = true; }
}, [query]);
return (
<>
<input value={query} onChange={e => setQuery(e.target.value)} />
<ul>
{data.entries.map(item => (
<li key={item._id}>
<p>{item.entry_description}</p>
</li>
))}
</ul>
</>
);
}
export default Search;

Related

next.js trouble with cookies

I need to save my data from array to cookies, but I have some issues. :)
At the begining here bellow my code:
import { LineChartProps } from "./LineChart.props";
import { Input } from "../Input/Input";
import { Button } from "../Button/Button";
import cookie from "js-cookie";
import React, { useState, useEffect } from "react";
export const LineChart = ({ }: LineChartProps): JSX.Element => {
const [inputValue, setInputValue] = useState("");
const [citiesList, setCitiesList] = useState<string[]>(
(cookie.get("citiesList")?.split(",")) || [
"London",
"Kyiv",
"Los Angeles"
]
);//array in state
const onChange = ((event) => {
setInputValue(event.target.value);
});
const onClick = ((event) => {
setCitiesList((currentArray) => [...currentArray, inputValue])
console.log(citiesList)
});
useEffect(() => {
cookie.set("citiesList", JSON.stringify(citiesList), { path: '' });
}, [citiesList]);
return (
<div>
<Button appearance="primary" onClick={onClick}>click me</Button>
<Input type="text" name="search" onChange={onChange} />
<div>
<p>{inputValue}</p>
</div>
</div>
)
};
To simplify it I'll also add here screenshot of my code:
As you see I have an array with cities. I want to save my array data to cookies, because I use next.js, but not a react.
I have button and input field:
So when I start my app, my array looks like this:
but for example if I reaload the page, I'll see this:
Then if I reload one more time, this:
So, what is better? Prevent cookies.set if all of elements are already in cookies, or there is other (better) solution for this?
And the second problem is: When I add some cities to my cookies Array, at the begining it looks like this:
But if I refresh the page twice, I'll see this:
Do you have any idea?
And if so, thanks in advance! Will be waiting for any solution:)
Try to use JSON.parse instead:
const [citiesList, setCitiesList] = useState<string[]>([]);
useEffect(() => {
const defaultCities = ["London","Kyiv","Los Angeles"]
const cities = cookie.get("citiesList");
if (!cities) {
setCitiesList(defaultCities)
} else {
try {
setCitiesList(JSON.parse(cities))
} catch (err) {
setCitiesList(defaultCities)
}
}
}, [])

React get value from key:value array

Beginner question. I know this is a simple question but I haven't been able to get this to work. I'm passing an object which holds an array of k:v pairs to a component. Eventually this props will contain multiple k:v pairs, but for now I'm just passing the one.
[{goal: 20000}]
In the component I'm trying to grab the value, 20000, so I can display it on screen. I can't seem to get just the number. If I look at props.goal I get the entire k:v.
[{goal: 20000}]
If I try props[0].goal I get 'TypeError: undefined is not an object (evaluating 'props[0].goal')'
What am I missing? Thanks for any help.
Update:
Here is the entire code for the component in question.
import { React, useState } from "react";
import Form from "react-bootstrap/Form";
import { Row, Col, Button } from "react-bootstrap";
import "./../css/Goal.css";
const Goal = (props) => {
// const [goal, setGoal] = useState("");
const [record, setRecord] = useState("");
const monthlyGoal = 2;
console.log("props[0]");
console.log(props[0]); //undefined
console.log("props");
console.log({ props }); //See below
props: Object
goal: Object
goals: [{goal: 20000}] (1)
const handleInput = (event) => {
console.log(event);
event.preventDefault();
setRecord(event.target.value);
console.log(record);
};
const defaultOptions = {
significantDigits: 2,
thousandsSeparator: ",",
decimalSeparator: ".",
symbol: "$",
};
const formattedMonthlyGoal = (value, options) => {
if (typeof value !== "number") value = 0.0;
options = { ...defaultOptions, ...options };
value = value.toFixed(options.significantDigits);
const [currency, decimal] = value.split(".");
return `${options.symbol} ${currency.replace(
/\B(?=(\d{3})+(?!\d))/g,
options.thousandsSeparator
)}${options.decimalSeparator}${decimal}`;
};
return (
<Form>
<Row className="align-items-center flex">
<Col sm={3} className="goal sm={3}">
<Form.Control
id="inlineFormInputGoal"
placeholder="Goal"
// onChange={(e) => setGoal(e.target.value)}
/>
<Button type="submit" className="submit btn-3" onSubmit={handleInput}>
Submit
</Button>
</Col>
<Col>
<h1 className="text-box">
Goal: {formattedMonthlyGoal(monthlyGoal)}
</h1>
</Col>
</Row>
</Form>
);
};
export default Goal;
Update 2:Here is the parent component:
import React, { useEffect, useState } from "react";
import Goal from "./Goal";
import axios from "axios";
const Dashboard = () => {
const [dashboardinfo, setdashboardinfo] = useState([]);
useEffect(() => {
async function fetchData() {
try {
const data = (await axios.get("/api/goals/getgoals")).data;
setdashboardinfo(data);
} catch (error) {
console.log(error);
}
}
fetchData();
}, []);
return (
<React.Fragment>
<Goal dashboardinfo={dashboardinfo} />
</React.Fragment>
);
};
export default Dashboard;
If you get an object like the following from console logging destructured props:
{
dashboardinfo: {goals: [{goal: 20000}]}
}
You need to use props.dashboardinfo.goals[0].goal to get the value.
Your props contains the object "dashboardinfo" so you need to do
props.dashboardinfo.goals[0].goal
or a better way is to destructure your props object like this
const Goal = ({dashboardinfo: { goals }}) => {
...
goals[0].goal
...
}
I believe I've resolved my issue. It wasn't so much a problem with accessing the key:value as I thought, because when the page was initialized I was able to grab the value and display it fine. However, when I refreshed the page I lost all of the props data and that resulted in an error. I tracked it down to the useState didn't seem to be updating the value before I was trying to read it. So I added a useEffect in the child component.
const Goal = (props) => {
const [goal, setgoal] = useState([]);
useEffect(() => {
setgoal(props.goal);
console.log("the goal", goal);
}, [props.goal, goal]);
...
This seems to have worked as I'm getting the information I want and not getting any errors when I refresh. This may not be the ideal way to go about this but it is working.

useState creates duplicates - infinite scrolling - Warning: Encountered two children with the same key

To give some insight what I am trying to archive:
So basically the second useEffect renders a navlist, clicking on a navlist-item should render the correct genre movie-list, it worked until, I tried to implement infinite scrolling... now after clicking the navlist-item, it always adds the movies from the nav-item before, which creates duplicates.
And I get this Errors:
Warning: Encountered two children with the same key. Also React tells me to add movies as dependency in the first useEffect. But if I do so, it triggers a infinite loop.
I think the core problem is: const newMovieList = [...movies, ...data.results] and the missing dependency.
Probably another problem is adding these many dependencies to a useEffect in the first place?
I tried for hours to fix it, but there always some weird side effects.. like one duplicate movie or some genre are working and others not.
Any advice or help, how to fix it, would be great.
import React, { useEffect, useState } from "react";
import { NavLink, useParams, useRouteMatch } from "react-router-dom";
import Movies from "./Movies";
export default function TestMovie(props) {
const [loading, setLoading] = useState(false);
const [genres, setGenres] = useState([]);
const [movies, setMovies] = useState([]);
const [pageNumber, setPageNumber] = useState(1);
const params = useParams();
const paramsId = params.id;
const route = useRouteMatch();
useEffect(() => {
(async () => {
setLoading(true);
try {
let response = "";
if (route.path === "/") {
response = await fetch(
`https://api.themoviedb.org/3/movie/upcoming?api_key=${apiKey}&page=${pageNumber}`
);
} else if (paramsId === "23") {
response = await fetch(
`https://api.themoviedb.org/3/movie/popular?api_key=${apiKey}&page=${pageNumber}`
);
} else {
response = await fetch(
`https://api.themoviedb.org/3/discover/movie?api_key=${apiKey}&with_genres=${paramsId}&page=${pageNumber}`
);
}
const data = await response.json();
const newMovieList = [...movies, ...data.results];
setMovies(newMovieList);
console.log("PageNumber: " + pageNumber);
} catch (error) {
console.log(error);
} finally {
setLoading(false);
}
})();
}, [setMovies, paramsId, route.path, pageNumber]);
useEffect(() => {
(async () => {
try {
const response = await fetch(
`https://api.themoviedb.org/3/genre/movie/list?api_key=${apiKey}`
);
const data = await response.json();
const newGenres = [{ id: 23, name: "Popular" }, ...data.genres];
setGenres(newGenres);
} catch (error) {
console.log(error);
}
})();
}, [setGenres]);
return (
<>
<ul className="genre-list">
<li className="genre-list__item">
<NavLink exact activeClassName="active" to="/">
Upcoming
</NavLink>
</li>
{genres.map((genre) => (
<li className="genre-list__item" key={genre.id}>
<NavLink
exact
activeClassName="active"
to={`/genre/${genre.id}-${genre.name}`}
>
{genre.name}
</NavLink>
</li>
))}
</ul>
<Movies
setPageNumber={setPageNumber}
movies={movies}
loading={loading}
></Movies>
</>
);
}
The InfiniteScroll part:
export default function Movies(props) {
const { movies, loading, setPageNumber } = props;
function updatePageNumber() {
setPageNumber((pageNumber) => pageNumber + 1);
}
return loading ? (
<div className="loader">
<Loader />
</div>
) : (
<>
<InfiniteScroll
dataLength={movies.length}
next={updatePageNumber}
hasMore={true}
>
<div className="movies-layout">
{movies.map((movie) => (
<Movie key={movie.id} movie={movie}></Movie>
))}
</div>
</InfiniteScroll>
</>
);
}
There might be different ways to do infinite scroll. Based on your way, the main issue is this line
const newMovieList = [...movies, ...data.results];
You can't be sure the new result should be the new one, because user could hit old page (or change "rows per page" settings).
In order to avoid that, maybe the easiest way for you is to make a function to add item by item key.
function getNewMovieList(current, data) {
const movies = {}
[...current, ...data].forEach(item => {
movies[item.id] = item
})
return Object.values(movies)
}
Literally you want to make sure it's still a new list without duplicated items. The error you are getting actually serve you good in this case.
Another way
Another way is to put fetch call inside a page component,
const Page = ({ pageId }) => {
const [data, setData] = useState([])
useEffect(() => { go fetch data for this page ONLY })
}
Now if you go to <Page pageId={5} />, you won't run into the data duplication issue at all.
But it probably depends on how you expect from the user, if you want to have smooth scroll behavior, maybe your approach is still the way to go, but if you can afford user to click any page, then second approach might be much safer and scalable.

How to map an array of maps from firestore in react

I'm trying to navigate an array of orders stored in each "User". I am able to query and find ones that have orders but I'm not able to display them. I keep getting an error "Cannot read property 'map' of null". Where am I going wrong?
The image below shows how all the orders are stored in "order"
import React, { useState, useEffect } from "react";
import { Link } from "react-router-dom";
import { firestore } from "../../../FireBase/FireBase";
const OrdersAdmin = (props) => {
const [order, setOrder] = useState(null);
useEffect(() => {
const fetchOrder = async () => {
const doc = await firestore.collection("Users");
const snapshot = await doc.where("orders", "!=", []).get();
if (snapshot.empty) {
console.log("No matching documents.");
return <h1>No Orders</h1>;
}
var ans = [];
snapshot.forEach((doc) => {
console.log(doc.id, "=>", doc.data().orders);
setOrder(doc.data().orders)
});
};
fetchOrder();
}, [props]);
return (
<div className="adminOrders">
<h1>orders</h1>
{console.log(order)}
{order.map((orderItem) => (
<div className="singleOrder" key={orderItem.id}>
<p>{orderItem}</p>
</div>
))}
</div>
);
};
export default OrdersAdmin;
The issue is that the initial value of order is null. null does not have Array.prototype.map, therefore you get the error. Try updating your render to use conditional rendering to only attempt Array.prototype.map when order is truthy and an Array:
{order && order.length > 0 && order.map((orderItem) => (
<div className="singleOrder" key={orderItem.id}>
<p>{orderItem}</p>
</div>
))}
Otherwise you can use a better default value of an empty array for order which would have Array.prototype.map available to execute:
const [order, setOrder] = useState([]);
Hopefully that helps!

ReactJS - Debouncing with React Hooks - Throws Error I can not solve. Parse errors

I am using this:
https://dev.to/gabe_ragland/debouncing-with-react-hooks-jci
But when I add it to my React Project I am getting this issue:
Line 4:20: Parse errors in imported module './BusNew': JSX attributes must only be assigned a non-empty expression (67:17) import/no-named-as-default
Line 4:20: Parse errors in imported module './BusNew': JSX attributes must only be assigned a non-empty expression (67:17) import/no-named-as-default-member
Here is my code:
import React, { Component, useState, useEffect } from 'react';
import useDebounce from './use-debounce';
export class BusNew extends Component {
return (
<div>
<input
placeholder="Search Marvel Comics"
onChange={e => setSearchTerm(e.target.value)}
/>
</div>
{isSearching && <div>Searching ...</div>}
{results.map(result => (
<div
key={result.id
>
<h4>{result.title}</h4>
))}
</div>
);
}
function Search() {
// State and setters for ...
// Search term
const [searchTerm, setSearchTerm] = useState('');
// API search results
const [results, setResults] = useState([]);
// Searching status (whether there is pending API request)
const [isSearching, setIsSearching] = useState(false);
// Debounce search term so that it only gives us latest value ...
// ... if searchTerm has not been updated within last 500ms
// As a result the API call should only fire once user stops typing
const debouncedSearchTerm = useDebounce(searchTerm, 500);
useEffect(
() => {
if (debouncedSearchTerm) {
setIsSearching(true);
searchCharacters(debouncedSearchTerm).then(results => {
setIsSearching(false);
// Filter out results with no thumbnail
const filteredResults = results.data.results.filter(
result => result.thumbnail.path.indexOf('image_not_available') === -1
);
setResults(filteredResults);
});
} else {
setResults([]);
}
},
[debouncedSearchTerm] // Only call effect if debounced search term changes
);
}
function searchCharacters(search) {
const apiKey = 'f9dfb1e8d466d36c27850bedd2047687';
return fetch(`https://gateway.marvel.com/v1/public/comics?apikey=${apiKey}&titleStartsWith=${search}`, {
method: 'GET'
}).then(r => r.json());
}
export default BusNew;
Sandbox example:
https://codesandbox.io/s/usedebounce-7byqd
Thank you for any help!

Resources