How to pass objects and states between two components in React - reactjs

I want to move the value of an object received by an API from one component to another.
I would like to use this "countryInfo.todayCases" inside SubCard component in App.js file as prop. For example where it says in App.js <SubCard title="Cases" cases={countryInfo.todayCases} total={2000} /> but I couldn't access this info from Header.js. I have googled everywhere and couldn't find something similar to my case. Your help is much appreciated
App.js
import Header from "./components/Header";
import SubCard from "./components/SubCard";
function App() {
return (
<div className="app">
<div className="app__left">
<Header />
<div className="app__stats">
<SubCard title="Cases" cases={_cases} total={2000} />
<SubCard title="Recovered" cases={4000} total={2000} />
<SubCard title="Deaths" cases={4000} total={2000} />
</div>
<Map />
</div>
<div className="app__right_bar">
<SideBar />
{/* Graph */}
</div>
</div>
);
}
export default App;
Header.js
function Header({ _cases, _recovered, _deaths }) {
const [countries, setCountries] = useState([]);
const [country, setCountry] = useState("worldwide");
const [countryInfo, setCountryInfo] = useState({});
const _cases = countryInfo.todayCases;
const _recovered = countryInfo.todayRecovered;
const _deaths = countryInfo.todayDeaths;
useEffect(() => {
// async -> send a request, wait for it and do something
const getCountriesData = async () => {
await fetch(`${COUNTRIES_URL}`)
.then((response) => response.json())
.then((data) => {
const countries = data.map((country) => ({
name: country.country, //country name ex United Kingdom
value: country.countryInfo.iso2, // country code ex: UK
}));
setCountries(countries);
});
};
getCountriesData();
}, []);
const onCountryChange = async (event) => {
const countryCode = event.target.value;
setCountry(countryCode);
const url =
countryCode === "worldwide"
? WORLDWIDE_URL
: `${COUNTRIES_URL}/${countryCode}`;
await fetch(url)
.then((response) => response.json())
.then((data) => {
setCountry(countryCode);
setCountryInfo(data);
});
};
console.log("Country info here >>>", countryInfo);
return (
<div className="app__header">
<h1>Covid 19 tracker</h1>
<FormControl className="app__dropdown">
<Select variant="outlined" onChange={onCountryChange} value={country}>
<MenuItem value="worldwide">Worldwide</MenuItem>
{countries.map((country) => (
<MenuItem value={country.value}>{country.name}</MenuItem>
))}
</Select>
</FormControl>
</div>
);
}
export default Header;
SubCard.js
function SubCard({ title, cases, total }) {
return (
<div>
<Card className="sub_card">
<CardContent>
{/*Title */}
<Typography color="primary">{title}</Typography>
{/*Number of Cases */}
<h2 className="card_cases">{cases}</h2>
{/*Total */}
<Typography className="card_total" color="textSecondary">
{total} Total
</Typography>
</CardContent>
</Card>
</div>
);
}
export default SubCard;

It appears that App calls Header and Subcard
/-> Header
App ->
\-> SubCard
In order for props to pass through to each component there are three options:
Move the shared data to the Parent
If you move the shared data to the Parent (App), then you can share that data with both its children as props.
Parent > Child > GrandChild
Change the components so that the data flows down through props from the Parent > Child > GrandChild. Then the order of the components would be
App -> Header -> SubCard
React Context
You could use React Context to create a global variable to share between the components.
With any of these three choices, you need to rebalance how the code is laid out between the components.

Related

How to prevent my refinementList component from rerendering and losing state?

I am using react instant search library and my issue is that my custom refinement list components loses its selections when I open modal.
I control my modal with useState:
const [modalIsOpen, setModalIsOpen] = useState(false);
Everytime I call setModalIsOpen(true); the refinements reset.
My custom refinement list component:
const RefinementList = ({ items, refine }: RefinementListProvided) => {
// return the DOM output
return (
<div className="">
{items.map(({ value, label, count, isRefined }: any) => (
<div key={value}>
<motion.button
onClick={() => {
refine(value);
}}
className={``}
>
<div className="">
{label}
</div>
</motion.button>
</div>
))}
</div>
);
};
I connect it with connectRefinementList
const CustomRefinementList = connectRefinementList(RefinementList);
This is my main jsx:
<InstantSearch searchClient={searchClient} indexName="foods">
<CustomSearchBox />
<CustomRefinementList
transformItems={(items) => orderBy(items, "label", "asc")} // this prevents facets jumping
attribute="tags"
/>
<InfiniteHits hitComponent={Hit} cache={sessionStorageCache} />
<ModalForMealPreview
handleOpen={modalIsOpen}
handleClose={handleModalClose}
/>
</InstantSearch>
What can I do to persist state or prevent RefinementList component from rerendering?
Here is a basic Example of React.memo, this will help your code
import React, { useEffect, useState } from "react";
const MemoComp = React.memo(({ ...props }) => <Test {...props} />); // Main Area to watch
function ClassSearch() {
const [state, setState] = useState(1);
return (
<div>
<button onClick={() => setState(state + 1)}>Increase</button> <br />
<MemoComp data="memorized" /> <br />
<Test data="original" /> <br />
</div>
);
}
export default ClassSearch;
const Test = ({ data }) => {
const date = new Date().getTime();
return (
<>
Test {date} {data}
</>
);
};

React Duplicate components updating wrong state: hooks

I'm a newbie to react, only been using it for a few days, so forgive me if this is a stupid question.
I have a file input component and an image thumbnail component, I use two duplicate file input components to update two different states then display the image from the different states in two different thumbnail components. I have unique keys set on all of the components, but only the state for the first component in the Dom is updated. When I add an image using the second file input, it updates the state belonging to the first file input.
I've tried looking for solutions and all of them state to use unique keys, which I think I have done properly.
let [certification, setCertification] = useState(null)
let [photoId, setPhotoId] = useState(null)
let handleUpdateCertificate = (e) =>{
let file = e.target.files[0]
console.log(file)
let path = URL.createObjectURL(file)
let newCertificate = {
'file': file,
'path' : path
}
setCertification(newCertificate)
}
let handleUpdatePhotoId = (e) => {
let file = e.target.photoidinput.files[0]
let path = URL.createObjectURL(file)
let newPhotoID = {
'file': file,
'path' : path
}
setPhotoId(newPhotoID)
}
My return html is:
<div className='justify-content-center margin-20' key='certificate-wrapper'>
<ImgThumbnail key={'certificate'} name={'certificate'} image=
{certification?.path} wrapperClass={'justify-content-center margin-20'}/>
</div>
<div className='justify-content-center margin-20'>
<FileInput key={'certificateinput'} name={'certificateinput'} labelText={<p
className='text-paragraph edit-btn-text'>Add Certificate</p>}
onChange={handleUpdateCertificate}
classWrapper={'edit-profile-responsive-btn-wrapper'}/>
</div>
<div className='justify-content-center margin-20 ' key='photo-Id'>
<ImgThumbnail key={'photoid'} name={'photoId'} image={photoId?.path}
wrapperClass={'justify-content-center margin-20'}/>
</div>
<div className='justify-content-center margin-20' key='photo-id-input-wrapper'>
<FileInput key={'photoidinput'} name={'photoidinput'} labelText={<p
className='text-paragraph edit-btn-text'>Add Photo ID</p>}
onChange={handleUpdatePhotoId}
classWrapper={'edit-profile-responsive-btn-wrapper'}/>
</div>
Okay I'll give you some hints and then give you the working example:
You don't need to set key attribute if you are writing JSX elements like that, you need that only if you render a list of elements from an array, to prevent useless re-rendering when the array updates.
use const instead of let when a variable is static, there is a lint rule about it !
Try to use DRY, your update Handlers share a lot of logic, if you are going to add more inputs that would be all code repetition.
Now the code:
import React, { useState } from 'react';
import './style.css';
export default function App() {
const [certification, setCertification] = useState(null);
const [photoId, setPhotoId] = useState(null);
const updateData = (file, cb) => {
const path = URL.createObjectURL(file);
const data = {
file: file,
path: path,
};
cb(data);
};
const handleUpdateCertificate = (e) => {
updateData(e.target.files[0], setCertification);
};
const handleUpdatePhotoId = (e) => {
updateData(e.target.files[0], setPhotoId);
};
return (
<div>
{certification && (
<div className="justify-content-center margin-20">
<ImgThumbnail
name={'certificate'}
image={certification?.path}
wrapperClass={'justify-content-center margin-20'}
/>
</div>
)}
<div className="justify-content-center margin-20">
<FileInput
id="certificate"
name={'certificateinput'}
labelText={
<p className="text-paragraph edit-btn-text">Add Certificate</p>
}
onChange={handleUpdateCertificate}
classWrapper={'edit-profile-responsive-btn-wrapper'}
/>
</div>
{photoId && (
<div className="justify-content-center margin-20 " key="photo-Id">
<ImgThumbnail
name={'photoId'}
image={photoId?.path}
wrapperClass={'justify-content-center margin-20'}
/>
</div>
)}
<div
className="justify-content-center margin-20"
key="photo-id-input-wrapper"
>
<FileInput
id="photo"
name={'photoidinput'}
labelText={
<p className="text-paragraph edit-btn-text">Add Photo ID</p>
}
onChange={handleUpdatePhotoId}
classWrapper={'edit-profile-responsive-btn-wrapper'}
/>
</div>
</div>
);
}
const FileInput = ({ id, labelText, ...props }) => (
<label htmlFor={id}>
{labelText}
<input id={id} style={{ display: 'none' }} type="file" {...props} />
</label>
);
const ImgThumbnail = ({ name, image }) => (
<div>
<img style={{ width: '100px', height: '100px' }} src={image} alt={name} />
</div>
);
This example works right, you were probably doing something wrong inside FileInput Component, remember that a label has to have an htmlFor attribute with the id of the input element you want to trigger.
Now, this code can be optimized and made more React style, since you might have more file inputs in the future, let's see how it can be optimized by creating reusable Components and compose them properly:
import React, { useState } from 'react';
import './style.css';
/* INPUTS IMAGE TYPES */
const inputs = [
{ type: 'photo', name: 'photo', label: 'Photo' },
{ type: 'certificate', name: 'certificate', label: 'Certificate' },
{ type: 'anotherType', name: 'anotherName', label: 'Another Input' },
];
export default function App() {
return (
<div>
{inputs.map((data) => (
<ImagePreviewer key={data.type} data={data} />
))}
</div>
);
}
const FileInput = ({ id, labelText, ...props }) => (
<label htmlFor={id}>
{labelText}
<input id={id} style={{ display: 'none' }} type="file" {...props} />
</label>
);
const ImgThumbnail = ({ name, image }) => (
<div>
<img src={image} alt={name} />
</div>
);
const ImagePreviewer = ({ data: { type, name, label } }) => {
const [image, setImage] = useState(null);
const updateData = (file, cb) => {
const path = URL.createObjectURL(file);
const data = {
file: file,
path: path,
};
cb(data);
};
const handleUpdate = (e) => {
updateData(e.target.files[0], setImage);
};
return (
<div>
{image && (
<div>
<ImgThumbnail name={'name'} image={image?.path} />
</div>
)}
<div>
<FileInput
id={name}
name={name}
labelText={<p>Add {label}</p>}
onChange={handleUpdate}
/>
</div>
</div>
);
};
A working demo HERE.

My event onClick in my map does not work. Very strange behavior

My Onclick on bestmovies map does not work. If I place it on a H1, for example, works. onClick={handleClickMovie}
// imports....
const Movies = () => {
const [popularMovies, setPopularMovies] = useState([])
const [bestMovies, setBestMovies] = useState([])
const [showPopUp, setShowPopUp] = useState(false)
const handleClickMovie = () => {
setShowPopUp(console.log('Clicked'))
}
useEffect(() => {
async function getMovies() {
const responsePopularMovies = await getPopularMovies()
setPopularMovies(responsePopularMovies.results)
const responseBestMovies = await getBestMovies()
setBestMovies(responseBestMovies.results)
}
getMovies()
}, [])
return (
<div>
<Wrapper>
{showPopUp ? <MoviePopUp /> : null}
<h1>Filmes Populares</h1>
<Content>
{popularMovies.map(item => (
<MovieItem movie={item} />
))}
</Content>
<h1>Filmes Bem Avaliados</h1>
<Content>
{bestMovies.map(item => (
<MovieItem movie={item} onClick={handleClickMovie} />
))}
</Content>
</Wrapper>
</div>
)
}
export default Movies
MovieItem.js
import React from 'react'
import { Cover, Score, Title } from './MovieItem.styles'
const MovieItems = ({ movie }) => {
return (
<Cover key={movie.id}>
<img
src={`https://image.tmdb.org/t/p/original${movie.poster_path}`}
alt="capas"
/>
<Score>{movie.vote_average}</Score>
<Title>{movie.title}</Title>
</Cover>
)
}
export default MovieItems
try wrapping in a div
<Content>
{bestMovies.map(item => (
<div onClick={handleClickMovie}>
<MovieItem movie={item} onClick={handleClickMovie} />
</div>
))}
</Content>
As #anthony_718 answered, you are calling onClick on a JSX component. JSX components aren't in the DOM and don't have click events (although they can render HTML elements if they contain them).
If you want, you can also pass the props all the way up to an actual html element the <Cover> renders.
#anthony_718's answer is correct.
The reason it didn't work it's because <MovieItem> doesn't have onClick in his props.
However, to facilitate reusability, you can modify your component like so:
const MovieItems = ({ movie, onClick }) => {
return (
<div onClick={onClick}>
<Cover key={movie.id}>
// ... rest of your stuff
</Cover>
</div>
)
}
export default MovieItems
It's essentially the same solution, but by placing <div onClick> within the component definition, you make it more reusable than the other option.
check this
bestMovies.map((item, i) => { return( <MovieItem movie={item} onClick={handleClickMovie} /> )})

Render Items from useState Hook React

I have made a call to my api using useEffect and stored the array of items using useState hook but I'm finding it difficult to render those items into a custom component which will also have the data passed.
Here's my react snippets:
export default function CreateCast() {
const [open, setOpen] = useState(false);
const [bibleCastItems, setBibleCastItems] = useState([]);
const classes = useStyles();
const fabStyle = {
bottom: 50.0,
right: 30.0,
position: "fixed"
};
useEffect(()=>{
async function fetchData(){
var items = await APIService.getAllBibleCasts();
// console.log(items);
// console.log(items.data.bibleCasts);
setBibleCastItems([items.data.bibleCasts]);
// items.data.bibleCasts.length > 0 ? setBibleCastItems([items.data.bibleCasts])
// : setBibleCastItems([]);
}
fetchData();
}, []
);
// console.log('bibleCastItems length ' + bibleCastItems.length);
return (
<GridContainer>
<GridItem xs={12} sm={12} md={12}>
<Card plain>
<CardHeader plain color="primary">
<div className={classes.container}>
<div className={classes.left}>
<h4 className={classes.cardTitleWhite}>All BibleCasts</h4>
<p className={classes.cardCategoryWhite}>
Powered by our friends from <b>Unicorn Tech Consultants</b>{" "}
</p>
</div>
</div>
</CardHeader>
<CardBody>
{
bibleCastItems.map((item, index) => <CastItem key={index} bibleCast={item[index]}/>) // this is where I'm facing issue
// bibleCastItems.map((item, index) => {
// console.log(item);
// setMyItem(item);
// return <div key={index}>{index}</div>
// })
}
<div className={classes.right}>
<Fab style={fabStyle} onClick={handleClickOpen}>
<AddIcon />
</Fab>
<UploadFormDialog
open={open}
handleClose={handleClose}
/>
</div>
</CardBody>
</Card>
</GridItem>
</GridContainer>
);
function handleClickOpen(){
setOpen(true);
};
function handleClose(){
setOpen(false);
};
}
Here's my state from browser view:
[![state view][1]][1]
How do I map this state to be a list of components? I'm confused about it
As you can see, I'm using a dialog to create new items and close the dialog once the request is successful. I have one doubt here, how do I tell the main component hosting the dialog that the new data has been fetched and should be added to the state?
My main question here is how to map the items in state to return a list of <CastItem /> component
CastItem Component Snippet
export default function CastItem(props) {
let {bibleCast} = props;
const classes = useStyles();
return <GridContainer>
<GridItem xs={12} sm={6} md={4}>
<Card>
<CardHeader color="info" stats icon>
<CardIcon color="info">
<Streams />
</CardIcon>
</CardHeader>
<CardBody>
<h3 className={classes.cardTitle}>{bibleCast.title}</h3>
<p className={classes.cardCategory}> Reinhard Bonnke</p>
</CardBody>
</Card>
</GridItem>
</GridContainer>
}
CastItem.propTypes = {
bibleCast: PropTypes.object.isRequired,
}
JSON Response from API in console:
[![json response][2]][2]
If you were to create a state variable to represent this response as a list and display that list, how would you go about it, using hooks. Thank you.
[1]: https://i.stack.imgur.com/QkthN.png
[2]: https://i.stack.imgur.com/8Hf11.png
Mistake you are doing is in CreateCast component , form api you are already getting an array again you are passing it inside an array, so it is coming as nested array
Do like this
useEffect(()=>{
async function fetchData(){
var items = await APIService.getAllBibleCasts();
setBibleCastItems(items.data.bibleCasts);
}
fetchData();
}, []
);
For Maping do like this
{
bibleCastItems.map((item, index) => <CastItem key={index} bibleCast={item}/>)
}
// For question how to update parent from child follow below
There are two ways you can set data in a parent component , one is refetch from the api or pass from children to parent and update the state there
I have an example here how to update parent and children,to add names to a list,name list state is maintained in parent component here and child will pass back value to parent by adding name
import { useState } from "react";
import Child from "./Child";
export default function Parent() {
const [list, setList] = useState(["ram"]);
const handleAddName = (name) => {
if (name) {
setList([name, ...list]);
// or you can refetch the list from api here
}
};
return (
<div>
<div style={{ float: "left" }}>
<h1>I am a parent Component</h1>
<ul>
{list &&
list.map((item) => {
return <li key={item}>{item}</li>;
})}
</ul>
</div>
<Child handleSubmit={handleAddName} />
</div>
);
}
Child
import { useState } from "react";
export default function Child(props) {
const [name, setName] = useState("");
const updateNameList = (name) => {
if (name) {
props.handleSubmit(name);
//reset field after data is sent
// you can also save data here making post request respective api
setName("");
}
};
return (
<div style={{ float: "right" }}>
<h1>I am a Child Component</h1>
<p> Add names below</p>
<br />
<input value={name} onChange={(e) => setName(e.target.value)} />
<button onClick={() => updateNameList(name)}>Add</button>
</div>
);
}
refer to this codesand box

How to pass state data to another webpage through react router link?

I am building an issue tracker app that is based on react/django. I am trying to pass projects, a state variable that is an array of project objects, to manageusers so that I can have access to the name of the project in manageusers. The reason behind this is so that I can use that project name to find the project associated with it, and assign users to the project. I am using a state object to pass projects to a link, but when I try to load the manageusers webpage it is stating that state is undefined. Keep in mind the projects data from the django backend is coming in fine, I am just trying to get that data onto manageusers.
Project
project.js
import { useState, useEffect } from "react";
import { Grid, TextField, Button, Typography } from "#material-ui/core";
import {
BrowserRouter as Router,
Link,
Route,
Switch,
} from 'react-router-dom';
const project = () => {
const [name, setName] = useState();
const [description, setDescription] = useState();
const [projects, setProjects] = useState([]);
const post = () => {
const requestOptions = {
method: "POST",
headers: { "Content-Type": "application/json"},
body: JSON.stringify({
name: name,
description: description,
}),
};
fetch("/api/newProject",requestOptions)
.then((response) => response.json())
.then((data) =>{
})
}
useEffect(() => {
fetch("/api/manageprojects")
.then((response) => response.json())
.then((data) =>{
setProjects(data)
})
}, [])
return (
<div>
<body>
<form action="#" method="POST">
<TextField onChange={(e) => setName(e.target.value)}> </TextField>
<br>
</br>
<TextField onChange={(e) => setDescription(e.target.value)}> </TextField>
<br>
</br>
<Button onClick={() => post()}> Create New Project </Button>
</form>
</body>
<div>
{projects && projects.map(project => (
<Grid item xs={3}>
<Typography component="h5" variant="h5">
<h5> {project.name} </h5>
<h5> {project.description} </h5>
</Typography>
</Grid>
))}
</div>
<Link to={{
pathname: '/manageusers',
state: {
projects: projects
}
}}>Manage Users
</Link>
</div>
);
}
export default project;
Manage Users
a portion of manageusers.js
const Name = (props) => {
const {projects} = props.location.state;
console.log(projects.name);
}
return (
<div>
<Select
value={selectedValue}
options={roles}
onChange={handleChange}
isOptionDisabled={option => option.isDisabled}
/>
<div class="ex1">
{role && role.map(roles => (
<Grid item xs={3}>
<Typography component="h5" variant="h5">
<h5> {roles.username} </h5>
</Typography>
</Grid>
))}
</div>
<Name/>
</div>
Instead of using React Router link,
what you need is a global store, such as Redux or Mobx. That way you can update the state with relevant data and retrieve them on the new page.
If you need a workaround, you could use history.pushState()
Basically you can push to your location and with it a state.
And on the next page, which is MyComp in this case, you could use history.state to retrieve the pushed data.

Resources