I am trying to return a placeholder for the 'null' prop values. I tried running a ternary operator and 'if' conditional statement to check if values exist before being rendered.
The props are passed from a parent component to a child component while data is being pushed to another child through react-router-dom's 'history' method.
When I run console.log the props are being passed properly however when I try to click on an image which contains 'null' I run into the current issue.
Carousel.js
import React from 'react';
import { Carousel } from 'react-responsive-carousel';
import "react-responsive-carousel/lib/styles/carousel.min.css";
import './Carousel.css';
const ImageCarousel = props => {
const photos = props.history.location.state.resources.photos;
return (
<>
<button className="backBtn" onClick={props.history.goBack}>Click to go back</button>
<Carousel>
{
photos.map(photo => {
return (
<div>
<img className='image' src={photo.url} alt="rental-carousel" />
</div>
)
})
}
</Carousel>
<div className="address-container">
<p className="address">Address:</p>
<p className="address-info">{' '}{props.history && props.history.location.state.address}</p>
</div>
</>
)
}
export default ImageCarousel;
You need to check if it's not null before getting photos value
const photos = props.history.location.state && props.history.location.state.resources ?
props.history.location.state.resources.photos :
[];
You can try the new ? optional chaining operator:
const photos = props?.history?.location?.state?.resources?.photos;
and then check if photos is undefined before rendering
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining
Check the null or undefined before assigning the prop
if (props === null ) {
......
}
const photos = props?.history?.location?.state?.resources?.photos;
Related
After onClick method to splice array, data seems to delete but page isn't updating. How to reRender or update the page to reflect the changes?
Home.js:
import React from "react";
import "./HomeStyles.css";
import HomeData from "./HomeData";
function Home() {
function handleDelete(id) {
var index = HomeData.map(function (e) {
return e.id;
}).indexOf(id);
HomeData.splice(index, 1);
}
return (
<>
<section className="home_section">
<div className="home_container">
{HomeData.map((item) => {
return (
<div className="Heading_container" key={item.id}>
<h1 className="home_heading">{item.heading} </h1>
<button onClick={handleDelete}>Delete</button>
</div>
);
})}
<button className="submit_btn">Submit</button>
</div>
</section>
</>
);
}
export default Home;
Data:
const HomeData = [
{
id: 1,
heading: 'This is first Heading'
},
{
id: 2,
heading: 'This is Second Heading'
},
]
export default HomeData;
I have tried using useNavigate from react-router-dom and history, but it didn't work.
In React functional components you can use a hook called useState. With this hook you can get and set the data however you want it.
const [data, setData] = useState(homeData);
Mutating state however is a big no-no in the React ecosystem because of the fact that it heavily practices the concept of immutability. Splice mutates the state by deleting or adding to the element itself.
Instead of mapping and splicing you can use filter with the setter. Filter is immutable, because it creates a shallow copy. You want to create a shallow copy, but without the item that has the id given as a parameter in your function. This would translate to the following code:
setData(homeData.filter(home => home.id !== id));
Now all you have to do is map through the state "data" instead of the homeData directly.
Maybe you can utilize state for this, can use useState hooks
It will be something like this:
import React, {useState} from "react";
import "./HomeStyles.css";
import HomeData from "./HomeData";
function Home() {
const [data,setData] = useState(HomeData)
function handleDelete(id) {
const newData = data.filter((e) => e.id !== id)
setData(newData)
}
return (
<>
<section className="home_section">
<div className="home_container">
[don't forget to use the state here] >>> {data.map((item) => {
return (
<div className="Heading_container" key={item.id}>
<h1 className="home_heading">{item.heading} </h1>
<button onClick={handleDelete}>Delete</button>
</div>
);
})}
<button className="submit_btn">Submit</button>
</div>
</section>
</>
);
}
export default Home;
Issue
In the current implementation the code is mutating an object that ins't part of any React state, so React isn't aware that anything needs to be rerendered.
Things to keep in mind:
Array.prototype.splice does an in-place mutation of the array it operates over.
The splice() method changes the contents of an array by removing or
replacing existing elements and/or adding new elements in place. To access part of an array without modifying it, see slice().
React components rerender for one of three reasons:
A local component state update is enqueued, component and sub-ReactTree rerender
A passed props value is updated, component and sub-ReactTree rerender
The parent component rerendered (because state and/or props updated)
Solution
To correctly render and update the HomeData array it necessarily should be part of a React component state. When updating React state, all state, and sub-state, necessarily needs to be a new object reference. This is because React uses a shallow reference equality check. It's far more common to use Array.prototype.filter to filter an existing array and return a new array reference.
Home Example:
import React from "react";
import "./HomeStyles.css";
import HomeData from "./HomeData";
function Home() {
const [homeData, setHomeData] = React.useState(HomeData); // <-- initialize state
const handleDelete = (id) => {
setHomeData(data => data.filter(el => el.id !== id)); // <-- filter and return new array
};
return (
<section className="home_section">
<div className="home_container">
{homeData.map((item) => ( // <-- map homeData state
<div className="Heading_container" key={item.id}>
<h1 className="home_heading">{item.heading}</h1>
<button
button="button" // <-- should be explicit with button type
onClick={handleDelete}
>
Delete
</button>
</div>
))}
<button
className="submit_btn"
type="submit" // <-- should be explicit with button type
>
Submit
</button>
</div>
</section>
);
}
export default Home;
You should use the useState hooks to update the view
import React, { useState } from "react"; //imported useState
import "./HomeStyles.css";
import HomeData from "./HomeData";
function Home() {
const [homeData, setHomeData] = useState(HomeData); //Added here
function handleDelete(id) {
const newData = homeData.filter((e) => e.id !== id)
setHomeData(newData)
}
return (
<>
<section className="home_section">
<div className="home_container">
{homeData.map((item) => { //changed state here
return (
<div className="Heading_container" key={item.id}>
<h1 className="home_heading">{item.heading} </h1>
<button onClick={handleDelete}>Delete</button>
</div>
);
})}
<button className="submit_btn">Submit</button>
</div>
</section>
</>
);
}
export default Home;
I have a problem with reading {find}.
The problem is in ProductDetail.js.
First, click on the products link than on any product to see details.
TypeError: Cannot read properties of null (reading 'find')
https://codesandbox.io/s/react-router-product-detail-pages-dynamic-links-forked-y1o0n?file=/src/ProductDetail.js:418-429
You've done some mistakes over there in your ProductDetail.js file.
First:
You can use useEffect hook to check and compare if there is a matching id or not.
Second:
You can use useState hook to store the thisProduct and update the thisProduct value by calling setThisProduct and use it in the JSXElement.
This is always a best practice to use the state for data set and get.
Here is more about React.Hooks
Third:
Price is a Object and you can't render your object like that, so use the key instead of object while rendering. like this: {thisProduct?.price?.current?.value}
You can learn more about optional chaining
Fourth:
productId which you're getting from useParams is a string type, and your productId from sneakers is a number type. So you need to change your productId to number while comparing like this: Number(productId)
Learn about Numbers in Js
Here is the complete code of yours:
// ProductDetail.js
import React, { useContext, useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import { StateContext } from "./GlobalContext";
function ProductDetail() {
const { productId } = useParams();
const { sneakers } = useContext(StateContext);
const [thisProduct, setThisProduct] = useState({});
useEffect(() => {
if (sneakers) {
const findProduct = sneakers.find((product) => {
return product.id === Number(productId);
});
console.log("findproduct", findProduct);
setThisProduct(findProduct);
}
}, [productId, sneakers]);
return (
<div>
{thisProduct && (
<>
<h1>{thisProduct?.name}</h1>
<p>Price: {thisProduct?.price?.current?.value}</p>
<p>{thisProduct?.description}</p>
</>
)}
</div>
);
}
export default ProductDetail;
completely check your state and props, it is not providing valid data to child component
<StateContext.Provider value={{ sneakers }}>
{console.log(sneakers, "== thisProduct")}
{children}
</StateContext.Provider>
console will show your data, it coming null so that is the issue
I am trying to build out a component in React which takes information from a JSON source, and uses some of that information to create states which can be passed down into other separate components. While I haven't passed my states into separate components yet, I have been able to get my state to update with the information from the JSON. However, when I load my page I get an error code which I want to sort out before continuing with my project in case there are unintended side effects from leaving the error in my code. The error code reads as following:
index.js:1 Warning: Each child in a list should have a unique "key" prop.
Check the render method of FetchData
in div (at FetchData.js:27)
in FetchData (at App.js:8)
in div (at App.js:7)
My App.js looks like this:
import React from 'react';
import './App.css';
import FetchData from './Components/FetchData/FetchData';
function App() {
return (
<div className="App">
<FetchData/>
</div>
);
}
export default App;
My FetchData.js looks like this:
import React from 'react';
class FetchData extends React.Component {
constructor() {
super();
this.state = {
portrait: null,
title: null
};
}
componentDidMount() {
fetch('https://randomuser.me/api')
.then (response => {
return response.json();
})
.then (data => {
let userImage = data.results.map((person) => {
return (
<div>
<img alt='portrait' img src={person.picture.large}/>
</div>
)
})
let userTitle = data.results.map((person) => { //line 27
return (
<div key={person.results}>
<div> {person.name.title} </div>
</div>
)
})
this.setState ({
portrait: userImage,
title: userTitle
})
console.log(this.portrait, this.title)
})
}
render() {
return (
<div className='box1'>
<div className='box2'>
<h2>{this.state.title}</h2>
{this.state.portrait}
</div>
</div>
)
}
};
export default FetchData;
and just in case since my index.js looks like this:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
serviceWorker.unregister();
I thought the issue was the fact that I used "person" into both my "data.results.map" so I tried to change the naming but that did not work either. Any help would be greatly appreciated!
The error is referring to your FetchData component.
The reconciliation algorithm in React can work if you assign an unique key to returned DOM objects. In this case, you are returning from the map function a list of similar DOM object. Any returned chunk have to declare the key attribute on the parent node.
In your case:
componentDidMount() {
fetch('https://randomuser.me/api')
.then (response => {
return response.json();
})
.then (data => {
let userImage = data.results.map((person) => {
return (
<div key={person.id}>
<img alt='portrait' img src={person.picture.large}/>
</div>
)
})
let userTitle = data.results.map((person) => { //line 27
return (
<div key={person.id}>
<div> {person.name.title} </div>
</div>
)
})
this.setState ({
portrait: userImage,
title: userTitle
})
console.log(this.portrait, this.title)
})
}
The key value must be an unique string and it is used by React to update the correct DOM nodes on state change. (I don't know how your person.results is filled, but you need a sort of ID)
For simple component you can also use this syntax
let userImage = data.results.map((person,idx) => {
return (
<div key={idx}>
<img alt='portrait' img src={person.picture.large}/>
</div>
)
})
Be aware using this syntax, because idx is the position of the current element in the containing array, and if used more than one time, it results in duplicate keys (And React will think that nodes with same key are the same nodes)
Using Index as a key is an anti-pattern in React.
Never using the index as a key in React, unless:
the list and items are static–they are not computed and do not change
the items in the list have no ids;
the list is never reordered or filtered.
When all of them are met, you may safely use the index as a key.
If not, please use the unique ID.
It may come from the elements you are going to display,
Or you can add a new ID property to your model or hash some parts of the content to generate a key.
The key only has to be unique among its siblings, not globally unique.
Read more here and here in React Docs
In your data.results.map((person)) function you need to add idx prop, so your code should look like:
let userImage = data.results.map((person,idx) => {
return (
<div key={idx}>
<img alt='portrait' img src={person.picture.large}/>
</div>
)
})
You can simply add the key attribute in the elements whenever you are using .map
let userImage = data.results.map((person, index) => {
return (
<div key={index}>
<img alt='portrait' img src={person.picture.large}/>
</div>
)
})
For the value of the key, it is preferred to use an unique id. If you do not have one, you can use index instead.
I am trying to understand how Props work in React. The following code is giving an error - Error: Objects are not valid as a React child (found: object with keys {args})
const App = () => {
const course = 'Half Stack application development'
return (
<div>
<Header args={course}/> // Will an object be passed or just the string?
</div>
)
}
const Header = (agrs)=>{
console.log(agrs)
return (
<div>
<h1>{agrs}</h1>
</div>
)
}
When props are being passed, is an Object is passed encapsulating the fields or just the field values are passed?
why does the above code doesn't work?
Thanks
First off, you have a spelling mistake. Replace agrs with args. Secondly, props are passed as an object (dictionary), so you have one of two options:
const Header = (props) =>{
console.log(props.args)
return (
<div>
<h1>{props.args}</h1>
</div>
)
}
or object destructuring:
const Header = ({args}) =>{
console.log(args)
return (
<div>
<h1>{args}</h1>
</div>
)
}
Also, make sure to add props validation (your linter should warn you about this):
import PropTypes from "prop-types";
Header.propTypes = {
args: PropTypes.string.isRequired
};
Answer 1: Value is passed as a key with the same name as field you assigned it to in props object.
Answer 2:
const Header = (props)=>{
console.log(props.agrs)
return (
<div>
<h1>{props.agrs}</h1>
</div>
)
}
The code above will run fine.
Alternative to answer 2:
const Header = ({agrs})=>{
console.log(agrs)
return (
<div>
<h1>{agrs}</h1>
</div>
)
}
This will also run fine.
It uses object destructuring so you don't have to use props.agrs but just args works fine.
const App = () => {
const course = 'Half Stack application development'
return (
<div>
<Header args={course}/> // Will an object be passed or just the string?
</div>
)
}
const Header = ({agrs})=>{
console.log(agrs)
return (
<div>
<h1>{agrs}</h1>
</div>
)
}
Use object Destructuring like above or
const Header = (props)=>{
console.log(props.agrs)
return (
<div>
<h1>{props.agrs}</h1>
</div>
)
}
Find more here Components and Props.
Find more about Destructuring
import React from 'react';
class App extends React.Component {
render() {
return (
<div>
<h1>{this.props.headerProp}</h1>
<h2>{this.props.contentProp}</h2>
</div>
);
}
}
export default App;
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App.jsx';
ReactDOM.render(<App headerProp = "Header from props..." contentProp = "Content
from props..."/>, document.getElementById('app'));
export default App;
enter image description here
I have an issue regarding react and I was hoping i could get some help. I will try my best to explain my situation and i will provide examples where needed.
The situation:
I have this component:
import React , {useState} from 'react';
import axios from 'axios';
import Ui from './UI';
function App() {
const [weather, setWeather] = useState({});
const [query, setQuery] = useState({query : ''});
const handleSubmit = (e) => {
e.preventDefault();
axios.get(`https://api.openweathermap.org/data/2.5/weather?q=${query.query}&units=metric&appid=appid`)
.then(res => {
setWeather({data: res.data})
});
};
const handleChange = (e) => {
setQuery({query:e.target.value});
};
return (
<div className="App">
<form onSubmit={handleSubmit}>
<input type="text" onChange = {handleChange}/>
<input type="submit" value = 'Search!'/>
</form>
<Ui weather ={weather}/>
</div>
);
}
export default App;
It's fetching data from the openweather API. When everything is set, I pass the Weather data to the presentational component named "Ui".
The data weather object that i pass down to the Ui has properties. One of these properties looks like 'weather.data.main'. When I try to access this in my presentational component I get an error.
TypeError: Cannot read property 'main' of undefined
But i am sure main exists. How is this possible ?
here's my presentational component :
import React , {useState} from 'react';
import axios from 'axios';
function Ui(weather) {
console.log(weather.data.main);
return (
<div className="Ui">
<h2>{}</h2>
</div>
);
}
export default Ui;
First issue
weather is a property of prop passed to Ui component so you need to either
destructure it
function Ui({ weather }) {
console.log(weather.data.main);
return (
<div className="Ui">
<h2>{weather.data.main}</h2>
</div>
);
}
Or use props.weather.data.main.
function Ui(props) {
console.log(props.weather.data.main);
return (
<div className="Ui">
<h2>{props.weather.data.main}</h2>
</div>
);
}
Second issue
TypeError: Cannot read property 'main' of undefined
Now to address the 2nd issue is that, the weather property might not be available at the time it was being passed to Ui component.
There are also two ways to fix this issue.
You can check & display a loading message/gif if the value you'd like to access (weather.data.main) is still unavailable or undefined.
(validating in the child level)
function Ui({ weather }) {
if (weather === undefined ||
weather.data === undefined ||
weather.data.main === undefined)
return <div>Loading weather data...</div>
return (
<div className="Ui">
<h2>{weather.data.main}</h2>
</div>
);
}
Or you can render Ui only when Weather data is available. (It basically depends on where in the component tree you'd like to display the Loading message/gif).
function App() {
// ... rest redacted for brevity
return (
// ... rest redacted for brevity
{weather && weather.data && <Ui weather ={weather}/>}
)
}
That oddly looking && chain instructs that App should display only when weather && weather.data is available.
Instead of having to use if statements I did in the Ui components in #1 above, && is just a short-hand.
Consider this:
import React , {useState} from 'react';
import axios from 'axios';
function Ui({ weather }) {
console.log(weather.data && weather.data.main);
return (
<div className="Ui">
<h2>{}</h2>
</div>
);
}
Note that: weather.data && this will check if weather actually has data, and then checks for the main inside that data.
You have to access weather like this
function Ui({ weather }) {
console.log(weather.data.main);
return (
<div className="Ui">
<h2>{}</h2>
</div>
);
}
Initially weather is equal to {} whiche doesn't have data.main. Hence you can do the following -
{weather.data && <Ui weather ={weather}/>}
This will render Ui only when weather.data is available (not before that).