Conditional rendering of React component if json key exists - reactjs

I am trying to conditionally render a component with bad results. I have read many tutorials and Stack Overflow questions but I can't get this work. Could you help me?
The conditional component is a data visualization geographical map which should be rendered only when a fetched json file has "code" key. In other words I have dozens of jsons and some of them include geo map information but not all. I have been trying boolean and different kind of ternary operators in jsx but every time when mapless item is clicked in sidebar React tries to render Map child component and gives an error that "code" (key) is undefined. What could be reason for that? Below is my code:
App.js
function App() {
const [files, setFiles] = useState([]);
const [items, setItems] = useState([]);
const [product_id, setProduct_id] = useState(13);
const [mapCode, setMapcode] = useState(0);
useEffect(() => {
fetch("/sidebar")
.then((res) => res.json())
.then((data) => {
setItems(data);
});
}, []);
useEffect(() => {
fetch(`/data/${product_id}`)
.then((res) => res.json())
.then((data) => {
setFiles(data);
if ("code" in data[0]) setMapcode(1);
else setMapcode(0);
console.log(mapCode);
});
}, [product_id]);
function HandleSelection(e) {
setProduct_id(e);
}
Inside useEffect if fetched product data includes "code" key I change mapCode every time an item (product) is clicked in sidebar. console.log(mapCode) gives right kind of results.
Below is the essential code inside return() of App.js. There are couple of ways I have tried to get the conditional rendering work.
<div className="col-6">
<Files files={files} />
</div>
<div className="col-3">
{/*Boolean(mapCode) && <Map files={files} />*/}
{/*mapCode === true && <Map files={files} />*/}
mapCode === true ? <Map files={files} /> : <div>No map</div>
</div>
I have been wondering if useEffect is the right place to use setMapcode(0) and setMapcode(1)?

First you should set a boolean value to isMapCode instead of a number:
const [isMapCode, setIsMapcode] = useState(false);
useEffect(() => {
fetch(`/data/${product_id}`)
.then((res) => res.json())
.then((data) => {
setFiles(data);
setIsMapcode(data[0].code !== undefined);
});
}, [product_id]);
Therefore you should check its value inside a scope ({}):
<div className="col-3">
{isMapCode ? <Map files={files} /> : <div>No map</div>}
</div>
Notice that you don't need another state for it, you can check your files state:
const isMapCodeAvailable = data[0].code !== undefined;
<div className="col-3">
{isMapCodeAvailable ? <Map files={files} /> : <div>No map</div>}
</div>

Related

stateful never[] array is still being able to assign data

I know that when passing an empty array to useState hook we should also provide the proper type, otherwise typescript would infer never[].
The following is something I did before knowing that and by my surprise is working:
It's a component intended to fetch some data, that data is of type any and will be stored in loadedUsers, which has a type of never[].
const AllUsers: FC<AllUsersProps> = () => {
const [isLoading, setIsLoading] = useState(true);
const [loadedUsers, setLoadedUsers] = useState([]);
useEffect(() => {
UserService.getAllUsers()
.then((response) => response.json())
.then((data) => {
console.log("users after retrieving data", data);
setIsLoading(false);
setLoadedUsers(data);
});
}, []);
Then I use loadedUsers to render a simple view
if (isLoading) {
return (
<section>
<p>Loading users</p>
</section>
);
}
return (
<div className="row">
<div className="col">
<h3>Users table</h3>
<UserTable users={loadedUsers} />
</div>
<div className="col">
<h3>Find user by id</h3>
<UserSearch />
</div>
</div>
);
};
Why am I not getting any errors during compilation? even in vscode there is no warning message

React state setting timing for conditional rendering

Currently, I am working on a simple React Exercise.
The I am trying to conditionally render a certain part of the jsx based on a certain state.
Basically, my code looks like this
const ShopList = (props) => {
const [isLoading, setIsLoading] = useState(false)
const [isEnd, setIsEnd] = useState(false)
const handleButtonClick = ()=>{
setIsLoading(true)
axios.get('https://codingapple1.github.io/shop/data2.json')
.then((result)=>{
setTimeout(()=>props.addData(result.data),2000)
})
.then(()=>{
setIsEnd(true)
})
.catch((e)=>{
console.log(e)
setIsLoading(false)
})
}
return(
<div className="container">
<div className="row">
{props.shoes.map((shoe,i) => (
<Product shoe={shoe} ind={i+1} key={shoe.id}/>
))}
</div>
{isLoading && <h3>Loading....</h3>}
{!isEnd && <button className="btn btn-light" onClick={handleButtonClick}>More Items</button>}
</div>
)
}
export default ShopList;
The thing is that I am having trouble locating my setIsLoading(false) so that I can hide the <h3>Loading...</h3> after two seconds.
In which part of the handleButtonClick function should I put setIsLoading(false)?
Answering your question, you most likely need to hide "Loading" in both cases:
if the request was successful and not.
So you could do this in the finally section like this:
axios.get(...)
.then(...)
.catch(...)
.finally(() => setIsLoading(false));

How to get single value in react-select dropdown

I am trying to create a multi-select dropdown indicator (the second element shown here) using react-select.
The purpose is to show all blog post categories on a blog page, and then to only render the blog posts that are selected in the dropdown indicator.
The tags are extracted from their posts based on a GraphQL query and stored in useState variables "tags" and "renderedPosts".
How do I simply get a value from the dropdown when a category is added or removed? Reading the react-select API, I get this:
getValue () => ReadonlyArray<...>
I don't know how to use that, VS Code simply screams when I try add an arrow function as an attribute in the Select.
I understand there is supposed to be a "value" by default on the Select but if I try to use it I get undefined.
I don't know if mapping to the default value is a problem or if it has anything to do with the ContextProvider (which was necessary). There are other attributes I cannot get to work either, like maxMenuHeight (which is supposed to take a number).
Allow me to share my code (you can probably ignore the useEffect):
export default function Blog({ data }) {
const { posts } = data.blog
const [tags, setTags] = useState([])
const [renderedPosts, setRenderedPosts] = useState([])
// Context necessary to render default options in react-select
const TagsContext = createContext();
// setting all tags (for dropdown-indicator) and rendered posts below the dropdown (initially these two will be the same)
useEffect(() => {
const arr = [];
data.blog.posts.map(post => {
post.frontmatter.tags.map(tag => {
if (!arr.some(index => index.value === tag)) {
arr.push({ value: tag, label: tag })
}
})
});
setTags([arr]);
setRenderedPosts([arr]);
}, []);
function changeRenderedPosts(value???){
setRenderedPosts(value???)
}
return (
<Layout>
<div>
<h1>My blog posts</h1>
<TagsContext.Provider value={{ tags }}>
<Select
defaultValue={tags.map(tag => tag) }
isMulti
name="tags"
options={tags}
className="basic-multi-select"
classNamePrefix="select"
// HOW DO I PASS THE VALUE OF THE ADDED/DELETED OPTION?
onChange={() => changeRenderedPosts(value???)}
maxMenuHeight= {1}
/>
</TagsContext.Provider>
// posts to be rendered based on renderedPosts value
{posts.map(post => {
EDIT: The closest I have now come to a solution is the following:
function changeRenderedTags(options){
console.log(options) //logs the remaining options
setRenderedTags(options) //blocks the change of the dropdown elements
}
return (
<Layout>
<div>
<h1>My blog posts</h1>
<TagsContext.Provider value={{ tags }}>
<Select
...
onChange={(tagOptions) => changeRenderedTags(tagOptions)}
I click to delete one option from the dropdown and I get the other two options in "tagOptions". But then if I try to change "renderedTags", the update of the state is blocked. I find this inexplicable as "setRenderedTags" has nothing to do with the rendering of the dropdown or its data!
with isMulti option true, you get array of options(here it's tags) from onChange callback. so I guess you could just set new tags and render filtered posts depending on the selected tags like below?
const renderedPosts = posts.filter(post => post.tags.some(tag => tags.includes(tag)))
...
onChange={selectedTags => {
setTags(selectedTags ? selectedTags.map(option => option.value) : [])
}}
...
I finally solved it - I don't know if it is the best solution but it really works! Sharing it here in case anyone else is in the exact same situation, or if you are just curious. Constructive criticism is welcome.
export default function Blog({ data }) {
const { posts } = data.blog
const [tags, setTags] = useState([])
const [renderedTags, setRenderedTags] = useState([])
const TagsContext = createContext();
useEffect(() => {
const arr = [];
posts.map(post => {
post.frontmatter.tags.map(tag => {
if (!arr.some(index => index.value === tag)) {
arr.push({ value: tag, label: tag })
}
})
});
setTags([...arr]);
}, [posts]);
useEffect(() => {
setRenderedTags([...tags]);
}, [tags])
return (
<Layout>
<div>
<h1>My blog posts</h1>
<TagsContext.Provider value={{ tags }}>
<Select
defaultValue={tags}
isMulti
name="tags"
options={tags}
className="basic-multi-select"
classNamePrefix="select"
onChange={(tagOptions) => setRenderedTags(tagOptions ? tagOptions.map(option => option) : [])}
value={renderedTags}
/>
</TagsContext.Provider>
{posts.map(post =>
(post.frontmatter.tags.some(i => renderedTags.find(j => j.value === i))) ?
<article key={post.id}>
<Link to={post.fields.slug}>
<h2>{post.frontmatter.title}</h2>
<p>{post.frontmatter.introduction}</p>
</Link>
<small>
{post.frontmatter.author}, {post.frontmatter.date}
</small>
<div dangerouslySetInnerHTML={{ __html: post.html }} />
</article>
: null
)}
</div>
</Layout>
)
}

React hook is not calling API after first load sometimes not at all

I have a very simple website that I'm using based off React, hosted on Firebase. On localhost and on my hosted site, I have ONE page that loads an API. Whenever I load that API, that page stops working usually after the first attempt, sometimes it doesn't work at all until I change the return statement to empty and back again. I was using class components and the error never occurred BUT I was unable to access a lot of the information in data.
The problem: My hook is not setting the state, data.
the error: TypeError: Cannot read property 'volumeInfo' of null
the question: How do I get my hook to call every time on page load?
cross reference error: No response from Fetch API in React useEffect hook I looked at this post, hopefully for guidance, but it's still not loading.
the code:
import '../App.css';
import Navigation from './Navigation';
import React, { useState, Component, useEffect } from 'react';
import { NavLink } from 'react-router-dom';
const Milestone1 = () => {
const [data, setData] = useState(null);
useEffect(() => {
const callAPI = async () => {
try {
fetch("https://www.googleapis.com/books/v1/volumes/Wfan6L9RGgYC")
.then((res) => res.json())
.then((data) => setData(data));
} catch (e) {
console.log(e);
}
};
callAPI();
}, []);
console.log(data);
return <>{(
<div className="school">
<header className="App-header">
</header>
<center>
<div className="navigation">
<Navigation />
</div>
<body className="App-body">
<body className="App-body">
<h2>Milestone 1</h2>
<h5>Title: {data && data.volumeInfo.title}</h5>
<img src={data.volumeInfo.imageLinks.smallThumbnail} alt="image" />
<h5>Authors: {data && data.volumeInfo.authors} Publish info: {data && data.volumeInfo.publisher} {data && data.volumeInfo.publishedDate} Country: {data && data.saleInfo.country}</h5>
<h5>Rating: {data && data.volumeInfo.averageRating}</h5>
</body>
<NavLink to="/Milestone1part2">Milestone 1 part two</NavLink><br></br>
</body>
</center>
</div>
)}</>;
}
export default Milestone1;
my other hook that's dumbed down:
const [data, setData] = useState(null);
useEffect(() => {
fetch("https://www.googleapis.com/books/v1/volumes/Wfan6L9RGgYC")
.then((res) => res.json())
.then((data) => setData(data));
}, []);
I've read similar posts, and usually they have a button in the page or something that can recall the API, but 1) it's not an interactive page 2) the whole page is not loading.
Thanks in advance.
You are getting this error because the data starts as null value, and then you try to render something like null.volumeInfo.imageLinks.smallThumbnail in here <img src={data.volumeInfo.imageLinks.smallThumbnail} alt="image" />
Try to add a loading component to handle the null data and to avoid this code {data && data.something}.
if (!data) {
return <div>Loadding...</div>;
}
Example:
https://codesandbox.io/s/reverent-firefly-ej0yk?file=/src/App.js:0-1276
import React, { useState, useEffect } from "react";
const Milestone1 = () => {
const [data, setData] = useState(null);
useEffect(() => {
const callAPI = async () => {
try {
fetch("https://www.googleapis.com/books/v1/volumes/Wfan6L9RGgYC")
.then((res) => res.json())
.then((data) => setData(data));
} catch (e) {
console.log(e);
}
};
callAPI();
}, []);
console.log(data);
if (!data) {
return <div>Loadding...</div>;
}
return (
<div className="school">
<header className="App-header"></header>
<center>
<div className="navigation"></div>
<body className="App-body">
<body className="App-body">
<h2>Milestone 1</h2>
<h5>Title: {data.volumeInfo.title}</h5>
<img src={data.volumeInfo.imageLinks.smallThumbnail} alt="image" />
<h5>
Authors: {data.volumeInfo.authors} Publish info:{" "}
{data.volumeInfo.publisher} {data.volumeInfo.publishedDate}{" "}
Country: {data.saleInfo.country}
</h5>
<h5>Rating: {data.volumeInfo.averageRating}</h5>
</body>
</body>
</center>
</div>
);
};
export default Milestone1;

React: "TypeError: undefined is not a function"

I am getting this problem TypeError: undefined is not a function and I did not recognize the error,
this is my code.
I have included the full code of this component in order to be clear
import React, {Component, useState,useEffect} from "react";
function Counter() {
const [searchTerm, setSearchTerm] = useState("");
const[materiel,setMateriels]=useState([]);
useEffect(() => {
fetch("http://localhost:5000/materiels")
.then((res) => res.json())
.then((data) => {
setMateriels(data);
console.log(materiels);
})
.catch(console.log);
}, []);
}
class searchMateriel extends Component {
render() {
return (
<div className="container">
<div className="col-xs-12">
<input type="text" placeholder="Search..." onChange={event => {
this.setState({searchTerm: event.target.value});
}}/>
{this.state.materiels
.filter((val) => val.nom.startsWith(this.statesearchTerm))
.map((val, key) => {
return (
<div className="user" key={{key}}>
<p>{val.nom}</p>
</div>
);
})}
</div>
</div>
);
}
state = {
materiels: [],
searchTerm: "",
}
componentDidMount() {
fetch('http://localhost:5000/materiels')
.then(res => res.json())
.then((data) => {
this.setState({materiels: data})
console.log(this.state.materiels)
})
.catch(console.log)
}
}
export default searchMateriel;
I have updated the code but still not working.
It is showing this error
Line 11:29: 'materiels' is not defined
The error is in the last line of my code, does anyone please have an idea?
Thanks in advance
Your code/use-case is wrong, in this code Counter is a functional component, so React except that to return some JSX's...
you get undefined because Counter returns nothing then you want to access the setSearchTerm method... basically, you will get the undefined is not a function react js error.
to store the input value in the state, you can pass the parent setState to the children or define a state in it.
NOTE: if you merely want to store your state in another place and process it, you can use hooks.
Counter in the code above is just a function, which actually doesn't return anything. Also Counter doesn't have setSearchTerm method which you are trying to access. You cannot use a hook outside of the top level of a functional component or hook see docs
As you are using class-based components, you should use setState method to update your state or switch to functional component and use useState hook
I don't see any usage of searchTerm in your code, but I am assuming, that you will add a function to your filter function.
class searchMateriel extends Component {
render() {
return (
<div className="container">
<div className="col-xs-12">
<input
type="text"
placeholder="Search..."
onChange={(event) => {
this.setState({ searchTerm: event.target.value });
}}
/>
{this.state.materiels
.filter((val) => val.nom.startsWith(this.statesearchTerm))
.map((val, key) => {
return (
<div className="user" key={{ key }}>
<p>{val.nom}</p>
</div>
);
})}
</div>
</div>
);
}
state = {
materiels: [],
searchTerm: "",
};
componentDidMount() {
fetch("http://localhost:5000/materiels")
.then((res) => res.json())
.then((data) => {
this.setState({ materiels: data });
console.log(this.state.materiels);
})
.catch(console.log);
}
}
export default searchMateriel;
Also, I wanted to mention that in class-based components state is merged when you are using setState but when you are using useState hook state is completely replaced
If you would like to replace your class-based component with functional one, it could look like this:
const searchMateriel = () => {
const [searchTerm, setSearchTerm] = useState("");
const [materiels, setMateriels] = useState([]);
useEffect(() => {
fetch("http://localhost:5000/materiels")
.then((res) => res.json())
.then((data) => {
setMateriels(data);
console.log(materiels);
})
.catch(console.log);
}, []);
return (
<div className="container">
<div className="col-xs-12">
<input
type="text"
placeholder="Search..."
onChange={(event) => {
setSearchTerm(event.target.value);
}}
/>
{materiels
.filter((val) => val.nom.startsWith(searchTerm))
.map((val, key) => {
return (
<div className="user" key={{ key }}>
<p>{val.nom}</p>
</div>
);
})}
</div>
</div>
);
};
export default searchMateriel;
More about useEffect you can read here
seeing you are not defining state in constructor. this may be the reason. Constructor loads before component is mounted. https://reactjs.org/docs/react-component.html#constructor

Resources