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
Related
The below code adds a next button to get the next 20 items from my backend, on clicking the button the data changes and I get my next 20 items, but the url does not change.
function PokemonList() {
const classes = useStyles();
let [pageNum, setPageNum] = useState(0);
const { loading, error, data } = useQuery(pokemonList, { variables: { pageNum: pageNum } });
function handleClick(e){
e.preventDefault();
setPageNum(parseInt(pageNum)+1)
}
if(error) {
return <h1> error</h1>;
}
if(loading) {
return <h1> loading</h1>;
}
return (
<div className="App">
{data.pokemonList.map((data) => (
<Card className={classes.card} variant='outlined'>
<CardHeader className={classes.titleHead} title={data.id} />
<CardMedia
className={classes.media}
component='img'
image={data.url}
title='image'
/>
<CardContent>
<Typography variant='body2' color='textSecondary' component='span'>
<p>{data.name}</p>
<br/>
<br/>
<br></br>
</Typography>
</CardContent>
</Card>
))}
<Link onClick={handleClick} className='characterlink2' to={`/pokemon/page/${parseInt(pageNum)+1}`}>
<button>
Next
</button>
</Link>
</div>
);
}
export default PokemonList;
How can I fix this? I am not sure that the "to" and "onClick" work together. How do I change the url along with the data?
Issue
e.preventDefault(); in the click handler prevents the default navigation action from occurring.
Solution
I don't see any reason for this action to be prevented, so I suggest removing this call to prevent the default action.
function handleClick(e){
setPageNum(page => page + 1);
}
Preferred solution
Assuming you've a route with path="/pokemon/page/:page" you should use the useParams hook and "sniff" the current page. This completely eliminates the need to synchronize the URL path and local React state, there's only one source of truth, the URL path.
import { useParams } from 'react-router-dom';
...
function PokemonList() {
const classes = useStyles();
const { page } = useParams();
const { loading, error, data } = useQuery(
pokemonList,
{ variables: { pageNum: page } },
);
if (error) {
return <h1>error</h1>;
}
if (loading) {
return <h1>loading</h1>;
}
return (
<div className="App">
{data.pokemonList.map((data) => (
...
))}
<Link
className='characterlink2'
to={`/pokemon/page/${Number(page) + 1}`}
>
<button type="button">Next</button>
</Link>
</div>
);
}
Created RenderCard component which creates multiple cards for given data.
I am creating bulk selection of card means selecting multiple cards. So when user click on circle present at the top left then that circle become check circle
So here the issue is that all card gets selected when user click on any card. So I want only that card select which users click
const Card = () => {
const [cardSelect, setCardSelect] = useState(false)
const onMouseEnter = () => {
console.log("onMouseEnter1")
}
const onMouseLeave = () => {
console.log("onMouseLeave1")
}
const logMessage = () => {
setCardSelect(prevCheck => !prevCheck);
}
const RenderCard = () => {
return album.map(item => {
return (
<Col className='container' onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} key={item.title} md='2'>
<Card>
<div>
<Link to={`/pages/blog/detail/${item.id}`}>
<CardImg className='img-fluid image' src={item.img} alt={item.title} top />
</Link>
</div>
<div className='select-card'>
{cardSelect ? <CheckCircle onClick={logMessage} /> : <Circle onClick={logMessage} />}
</div>
<CardBody>
<CardTitle className="image2 te" >
<b>{item.title}</b>
</CardTitle>
<CardText>{item.comment} Comments</CardText>
</CardBody>
</Card>
</Col>
)
})
}
return (
<>
<Row>
<RenderCard />
</Row>
</>
)
}
Anyone can point me in the right direction where I am doing mistakes.
Thanks
If i understand you correctly i would suggest that
you should wrap anything that is inside the return statement and refactor it to its own component, and then pass item as prop in that component and make the clickhandler inside that component. like this:
const RenderCard = () => {
return album.map(item => {
return ( <Component item={item} />)
}
Now the component have its own state that shows either its clicked or not clicked
With this line of code :
cardSelect ? <CheckCircle onClick={logMessage} /> : <Circle onClick={logMessage} />
You are saying : "If cardSelect is true, then render CheckCircle component, otherwise, render Circle component and do that for all items in album list". So when the state changes, it will render only one type of object CheckCircles, or Circles as many times as there are elements in the album list for which you are calling the map method.
That is why you are seeing all checked circles when you set your variable cardSelect to true.
Hi I have been using this package react-to-print to print document and it works really well. Passing value to child component works and I can print the dynamic data too. However, I am facing problem to pass dynamic data of array list. It always gets the last item of array. I wrote an example, please take a look at it
import * as React from "react";
import { useRef } from "react";
import ReactToPrint from "react-to-print";
const ComponentToPrint = React.forwardRef((props, ref) => {
const { value } = props;
return (
<div className="print-source" ref={ref}>
Number {value}
</div>
);
});
export default function App() {
const componentRef = useRef();
const numbers = [1, 2, 3, 4, 5];
return (
<>
{numbers.map(function (item, index) {
return (
<div style={{ display: "flex" }}>
<li key={index}>{item}</li>
<ReactToPrint
trigger={() => <button type="primary">Print</button>}
content={() => componentRef.current}
/>
<ComponentToPrint ref={componentRef} value={item} />
</div>
);
})}
</>
);
}
Live Demo
Whenever I click the print button, I expect to send the unique value of number to child component but every time I am getting the last value of array. What am I doing wrong?
Because there's just one componentRef instance, which on the order of rendering will have the last rendered value.
Instead each returned component from App needs to have its own instance of componentRef.
This can be achieved if you
make the returned html from App a component too (say ComponentToPrintWrapper)
have this component its own componentRef.
const ComponentToPrintWrapper = ({ item }) => { // 1.
const componentRef = useRef(); // 2.
return (
<div style={{ display: "flex" }}>
<li>{item}</li>
<ReactToPrint
trigger={() => <button type="primary">Print</button>}
content={() => componentRef.current}
/>
<ComponentToPrint ref={componentRef} value={item} />
</div>
);
};
Use ComponentToPrintWrapper on your App instead
...
export default function App() {
const numbers = [1, 2, 3, 4, 5];
return (
<>
{numbers.map(function (item, index) {
return <ComponentToPrintWrapper key={index} item={item} />;
})}
</>
);
...
}
This will ensure each return element has its own componentRef instead.
CodeSandbox
Still new to ReactJS.
I have 3 JS pages: Home, Create, Edit.
From Home, you can navigate to the Create and Edit pages.
You can access the Create page anytime, but you need to call an API to populate some data before you can access the Edit page.
All 3 pages are using the same component, FormEntry. As its name, it generates basically a form input. Within this component, there are 2 functions: Search and AddEdit. Home is using the former, Create and Edit are using the later.
The flow is as such where when you click on the Create button, this will direct you to the Create page. The Create page will then display the form.
However, if you click on the Search button, this will call an API and generate data in a table. Each table row is clickable and clicking on them will direct you to the Edit page. For reusability, I parse in some values using props that, in theory, should populate the form fields based on which row I clicked on.
The issue I'm having is that though the value gets parsed in, the form field is not displaying the correct data. To be specific, the data from the responseData I parsed into the component is not displaying. And even if it does display, it's returning as 'undefined'.
What am I doing wrong?
Home.js
function HomePage() {
const [responseData, setData] = useState([]);
const navigateData = useNavigate();
function navigateToEdit(event){
navigateData({insert URL here}+event.id);
}
function getSearchData2(allData){
( allData.propRefNum !== "" ||
allData.client !== "" ||
allData.appSys !== "" ||
allData.status !== "" ? AxiosCall.searchProposal(JSON.stringify(allData)): AxiosCall.getProposals()
)
.then(
(result) => {
setData(result.data);
}
);
}
return (
<>
<div style={{ height: 400, width: '100%' }}>
<div style={{ display: 'flex', height: '100%' }}>
<div style={{ flexGrow: 1 }}>
<DataGrid onRowClick={navigateToEdit} rows={dataRowObjs} columns={dataColObjs} headerAlign="center" disableColumnFilter />
</div>
</div>
</div>
</>
);
}
export default HomePage;
Edit.js
function EditPage() {
const [responseData, setData] = useState([]);
const { id } = useParams();
useEffect(() => {
const apiData = {
id: id
}
AxiosCall.getProposal(JSON.stringify(apiData))
.then(
(result) => {
setData(result.data);
}
);
},[]);
function getEditData(allData){
fetch({insert URL here}).then(
(result) => {
setData(result);
});
}
return <FormEntry.AddEditFormEntry title="Edit Proposal" defaultDataValue={responseData} responseInputData={getEditData} />
}
export default EditPage;
FormEntry component; AddEditForm
function AddEditFormEntry(props){
const propRefNumRef = useRef();
const descRef = useRef();
const clientRef = useRef();
const appSysRef = useRef();
const statusRef = useRef();
const remarkRef = useRef();
const vendorRef = useRef();
const { register, formState: { errors }, handleSubmit } = useForm();
function submitData(data){
//event.preventDefault();
const propRefNum = propRefNumRef.current.value;
const desc = descRef.current.value;
const client = clientRef.current.value;
const appSys = appSysRef.current.value;
const status = statusRef.current.value;
const remark = remarkRef.current.value;
const vendor = vendorRef.current.value;
const allData = {
propRefNum: propRefNum,
desc: desc,
client: client,
appSys: appSys,
status: status,
remark: remark,
vendor: vendor,
}
props.responseInputData(allData);
}
let defaultRefNum = props.defaultDataValue?.refNum; - **Note A: this is the line in question. When I console.log this variable, it displays the data correctly**
return(
<>
<form className="formEntry" onSubmit={handleSubmit(submitData)}>
<div style={{ display: 'flex'}} >
<div align="left" >
<RouterLink to={insert URL here} >
<IconButton aria-label="search" color="primary" >
<SkipPreviousIcon />
</IconButton>
</RouterLink >
</div>
<h1 style={{ flexGrow: 1, marginTop: -4 }} >{props.title}</h1>
<div align="right">
<IconButton aria-label="search" color="primary" type="submit" >
<SaveIcon />
</IconButton>
<IconButton aria-label="search" color="primary" type="submit" >
<SaveIcon />
</IconButton>
</div>
</div>
<br/>
<Stack justifyContent="center" direction="row" spacing={2} >
<Stack justifyContent="center" direction="column" spacing={2} >
<FieldEntry.TextEntry required="true" label="Proposal Reference Number" type="text" id="input_propRefNum" name="propRefNum" propsRef={propRefNumRef} value={defaultRefNum} />
**Referring to Note A above, I want to populate this field above. I am getting 'undefined', if not blank. If I am using the Create function, blank/undefined is expected. The Edit function is supposed to populate something here **
<FieldEntry.TextEntry label="Description" type="text" id="input_desc" name="desc" propsRef={descRef} value={props.defaultDataValue?.description} />
<FieldEntry.TextEntry required="true" label="Client" type="text" id="input_client" name="client" propsRef={clientRef} />
<FieldEntry.TextEntry required="true" label="Application System" type="text" id="input_appSys" name="appSys" propsRef={appSysRef} value={props.defaultDataValue?.appSystem} />
</Stack>
</Stack>
<br/>
</form>
</>
)
}
Note: I've removed a number of codes that does not pertain to the matter, to keep the sample code small. Rest assured that aside from my issue, everything is working as expected
I think a simpler example(which I just found out will have the same issue) would be
const [defaultRefNum, setRefNum] = useState("");
const [counter, setCounter] = useState(0);
let testValue = props.defaultDataValue?.refNum
useEffect(() => {
console.log("testValue2:",testValue)
if (props.defaultDataValue != null){
console.log("is not Null")
setCounter(c => c+1);
}
else{
console.log("is Null")
}
},[testValue]);
console.log("counter:",counter)
and
<FieldEntry.TextEntry value={counter} />
console.log output - counter: 2
Value in TextEntry: 0
I would assume the value in TextEntry should've outputted to be 2.
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.