I am working with the map function in react and I am getting all my info to map except for my button ref. Below is the code. The data is getting pulled from a database and passed as a prop as well. When I filter the list I get the look I am looking for but when I go to select a doctor I only get the last name for the doctor that was the last item iterated, and this is across all the buttons when I click them. Where am I going wrong if I may ask.
import {
Button,
Card,
CardActions,
CardContent,
Grid,
Typography,
} from "#material-ui/core";
import { makeStyles } from "#material-ui/styles";
import { useRef } from "react";
const styles = makeStyles({
bound: {
margin: "40px 200px",
position: "relative",
},
card: {
maxWidth: "70%",
margin: "50px auto",
borderStyle: "solid",
borderRadius: "50px",
borderColor: "black",
borderWidth: "3px",
"&:hover": {
boxShadow: "black",
borderColor: "green",
},
},
null: {
display: "none",
},
btn: {
margin: "20px 0 0 0",
borderRadius: "20px",
"&:hover": {
backgroundColor: "green",
color: "white",
},
},
});
const SearchResult = (props) => {
const doctorList = props.doctor;
const selectedRef = useRef();
const selectedHandler = (e) => {
console.log(e.target.target);
console.log(selectedRef.current.value);
};
const doctorCard = (doctorList) => {
return (
<Card
variant="outlined"
className={`${doctorList ? classes.card : classes.null}`}
key={doctorList.user_id}
id={doctorList.user_id}
>
<CardContent>
<Grid container>
<Grid xs={6} md={6} lg={6} item>
<img src="#" alt="Img.png" />
</Grid>
<Grid xs={6} md={6} lg={6} item>
<Typography variant="h6">
Dr. {doctorList.first_name} {doctorList.last_name}
</Typography>
<Typography variant="h6">
Speciality: {doctorList.title}
</Typography>
<Typography variant="h6">
Primary Language: {doctorList.primary_language}
</Typography>
</Grid>
</Grid>
<CardActions>
<Button
variant="contained"
className={classes.btn}
type="submit"
ref={selectedRef}
value={doctorList.last_name}
onClick={selectedHandler}
size="large"
fullWidth
>
Select
</Button>
</CardActions>
</CardContent>
</Card>
);
};
const classes = styles();
return (
<div className={classes.bound}>{doctorList.doctor.map(doctorCard)}</div>
);
};
export default SearchResult;
OUTPUT
You are looping list and using common reference 'selectedRef' in you list component. Once list rendering finishes, last_name of last item in your array is being stored in 'selectedRef' reference. That's why whenever you click it's always getting last item.
To resolve this you might want to break component on granular level. Use two component ItemList and ListItem. ItemList will simply loop array and in ListItem component create reference to each item while looping. That should resolve your issue.
const ItemList = (props) => {
const renderList=(doctor)=>{
return <ListItem key={`doctor-${doctor.id}`} {...doctor}/>
}
return <div>{props.data.map(renderList)}</div>
}
const ListItem = () => {
// reference below create new reference for every item
const selectedRef = useRef();
// rest of logic to render doctor card
}
Related
I have an input field in child component. If you type in valid wallet address it should display wallet balance in parent component.
I don't know how to pass that e.target.value we are reading from input field.
I tried to do this function in parent component - Dashboard.
const [walletBalance, setWalletBalance] = useState('');
const getWalletBalance = async () => {
try {
const web3 = new Web3(Web3.givenProvider || 'ws://localhost:3000');
for (const wallet of dummyWallets) {
const balance = await web3.eth.getBalance(wallet.address);
console.log(balance);
const balanceConvertedFromWeiToEth = await Web3.utils.fromWei(balance, 'ether');
const shortenedBalance =
balanceConvertedFromWeiToEth.length >= 10
? balanceConvertedFromWeiToEth.slice(0, 10)
: balanceConvertedFromWeiToEth;
setWalletBalance(shortenedBalance);
}
} catch (error) {
console.error(error);
throw new Error('No wallet found');
}
};
render (
<p>Wallet balance: {walletBalance}</p>
and then pass this function down to child component with input field (need to prop drill 3 times for that) and in child component say:
const [inputText, setInputText] = useState('');
const handleInputChange = (e) => {
setInputText(e.target.value);
if (inputText === wallet.address) {
getWalletBalance()
}
};
render (
<input value={inputText} onChange={handleInputChange} />
)
It didn't work. No errors. Just nothing happens.
Then I thought that I need to do the other way around: Instead of trying to pass getWalletBalance() down as props, I need to read data from input field and pass that data to parent component, because parent needs to have access to the input field and read from it to be able to display wallet balance in parent.
I've also tried to create context:
import React, { useState, createContext } from 'react';
import { dummyWallets } from '../mocks/dummyWallets';
import Web3 from 'web3';
export const WalletContext = createContext();
// eslint-disable-next-line react/prop-types
export const TransactionProvider = ({ children }) => {
const [inputText, setInputText] = useState('');
const [walletBalance, setWalletBalance] = useState('');
const handleInputChange = async (e) => {
setInputText(e.target.value);
const web3 = new Web3(Web3.givenProvider || 'ws://localhost:3000');
for (const wallet of dummyWallets) {
if (inputText === wallet.address) {
const balance = await web3.eth.getBalance(wallet.address);
console.log(balance);
const balanceConvertedFromWeiToEth = await Web3.utils.fromWei(balance, 'ether');
const shortenedBalance =
balanceConvertedFromWeiToEth.length >= 10
? balanceConvertedFromWeiToEth.slice(0, 10)
: balanceConvertedFromWeiToEth;
setWalletBalance(shortenedBalance);
}
}
};
return (
<WalletContext.Provider
value={{ walletBalance, handleInputChange, inputText, setInputText,
setWalletBalance }}>
{children}
</WalletContext.Provider>
);
};
Then pass handleInputChange to onChange in child component and inputText as value, then use walletBalance in parent component.
All context things walletBalance, handleInputChange, inputText, setInputText and setWalletBalance threw undefined error.
Here is child component consuming data from context:
import React, { FC, useState, useContext } from 'react';
import { Formik, Form, Field } from 'formik';
import { Button, InputAdornment, Box, Grid } from '#material-ui/core';
import TextField from '#eyblockchain/ey-ui/core/TextField';
import { Search } from '#material-ui/icons';
import SvgEthereum from '../../../images/icon/Ethereum';
import { makeStyles } from '#material-ui/styles';
import { Theme } from '#material-ui/core';
import { dummyWallets } from '../../../shared/mocks/dummyWallets';
import Web3 from 'web3';
import { WalletContext } from '../../../shared/context/WalletContext';
const etherIconBorderColor = '#eaeaf2';
const useStyles = makeStyles((theme: Theme) => ({
etherIconGridStyle: {
padding: '16px 0 0 0'
},
etherIconStyle: {
fontSize: '220%',
textAlign: 'center',
color: theme.palette.primary.light,
borderTop: `solid 1px ${etherIconBorderColor}`,
borderBottom: `solid 1px ${etherIconBorderColor}`,
borderLeft: `solid 1px ${etherIconBorderColor}`,
padding: '5px 0 0 0',
[theme.breakpoints.down('sm')]: {
fontSize: '150%',
lineHeight: '43px'
}
},
buttonStyle: {
paddingTop: theme.spacing(2)
},
searchIconStyle: {
color: theme.palette.primary.light
}
}));
const onSubmitHandler = () => {
return;
};
const SearchInputForm: FC<any> = (props) => {
const { walletBalance, handleInputChange, inputText } = useContext(WalletContext);
const classes = useStyles();
return (
<Formik
onSubmit={onSubmitHandler}
initialValues={{ name: '' }}
render={({ values }) => {
return (
<Form>
<Grid container>
<Grid item xs={1} className={classes.etherIconGridStyle}>
<Box className={classes.etherIconStyle}>
<SvgEthereum />
</Box>
</Grid>
<Grid item xs={11}>
<Field
required
name="name"
placeholder={props.placeholdervalue}
value={inputText}
component={TextField}
onChange={handleInputChange}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<Search fontSize="large" className={classes.searchIconStyle} />
</InputAdornment>
)
}}
/>
</Grid>
<Grid item xs={12}>
<Box
display="flex"
justifyContent="flex-end"
alignItems="flex-end"
className={classes.buttonStyle}>
<Button type="submit" variant="contained" color="secondary" size="large" disabled>
Create Alert
</Button>
</Box>
</Grid>
</Grid>
</Form>
);
}}
/>
);
};
export default SearchInputForm;
Here is parent component where I pull walletBalance from context:
const Dashboard: FC<any> = () => {
const { walletBalance } = useContext(WalletContext);
const classes = useStyles();
return (
<>
<Container maxWidth={false} className={classes.dashboardContainer}>
<Grid>
<Box data-testid="alert-page-heading">
<Typography variant="h3">Alerts</Typography>
</Box>
<Box data-testid="alert-page-subheading">
<Typography className={classes.subHeadingStyle}>
Monitor the Web 3.0 for interesting blockchain events
</Typography>
</Box>
</Grid>
<Grid container spacing={4}>
<Grid item xs={12} sm={8}>
<Box className={classes.dashboardCard}>
<Box className={classes.searchBox}>
<SearchTabs />
</Box>
<Box
sx={{
display: 'flex',
justifyContent: 'space-between'
}}>
<Box
sx={{
height: '60%',
padding: '1%',
fontSize: 20
}}>
<a href="http://localhost:3000/" className={classes.backToAlertsLink}>
Back to my alerts
</a>
<p className={classes.boldText}>Recent transactions</p>
</Box>
<Box sx={{ height: '60%', padding: '4%', fontSize: 20 }}>
<span>Wallet Balance</span>
<span className={classes.balanceNumber}>{walletBalance} ETH</span>
</Box>
</Box>
</Box>
</Grid>
<Grid item xs={12} sm={4}>
<Box className={classes.dashboardCard}>
<Box sx={{ height: '60%', padding: '2%', fontSize: 20, fontWeight: 'bold' }}>
Top 10 alerts
</Box>
</Box>
</Grid>
</Grid>
</Container>
</>
);
};
export default Dashboard;
The problem is that I pull handleChange from context and apply to input field in child component to read the value from the input and then pull walletBalance from context in parent but that walletBalance is not updated (doesn't contain input value in it).
Can someone please share knowledge on how to read data from child component input field and display something from that in parent component?
Any advice is appreciated. Thank you.
import React , {useContext, useEffect, useState} from 'react'
import { Button } from "#mui/material";
import Grid from '#mui/material/Grid';
import { styled } from '#mui/material/styles';
import Box from '#mui/material/Box';
import Paper from '#mui/material/Paper';
import AddIcon from '#mui/icons-material/Add';
import RemoveIcon from '#mui/icons-material/Remove';
import IconButton from '#material-ui/core/IconButton';
import axios from 'axios'
//import {ids} from "./Scanner"
let tot = 0;
let newproduct=[]
let ids=[{productid:'9501101530003'}]
const Item = styled(Paper)(({ theme }) => ({
backgroundColor: theme.palette.mode === 'dark' ? '#1A2027' : '#fff',
...theme.typography.body2,
padding: theme.spacing(1),
textAlign: 'center',
color: theme.palette.text.secondary,
}));
const Item2 = styled(Paper)(({ theme }) => ({
backgroundColor: theme.palette.mode === 'dark' ? '#1A2027' : '#fff',
...theme.typography.body2,
padding: theme.spacing(2.5),
textAlign: 'center',
color: theme.palette.text.secondary,
}));
function Product (props){
console.log("g")
console.log(props)
return( <Box sx={{ width: '100%' }}>
<Grid container rowSpacing={1} columnSpacing={{ xs: 1, sm: 2, md: 3 }}>
<Grid item xs={6}>
<Item2>{props.product.productName}</Item2>
</Grid>
<Grid item xs={6}>
<Item2>{props.product.price}</Item2>
</Grid>
{/* <Grid item xs={4} >
<Item>
<IconButton onClick={()=>{qty++}} color="primary">
<AddIcon fontSize='small'/>
</IconButton>
{qty}
<IconButton color="primary">
<RemoveIcon fontSize='small'/>
</IconButton>
</Item>
</Grid> */}
</Grid>
</Box>
)
}
export default function Cart (){
console.log(ids)
const [product,setproduct] = useState([])
useEffect(()=>{
ids.forEach(element => {
console.log(element)
axios.post('http://localhost:4000/app/barcode',element)
.then((response)=>{
console.log(response)
newproduct= product
newproduct.push(response.data.product)
setproduct(newproduct)
console.log(product)
product.forEach((prod)=>tot=tot+parseInt(prod.price))
console.log(tot)
})
});
})
return(
<div >
<Box sx={{ width: '100%' }}>
<Grid container rowSpacing={1} columnSpacing={{ xs: 1, sm: 2, md: 3 }}>
<Grid item xs={6}>
<Item>product name</Item>
</Grid>
<Grid item xs={6}>
<Item>price</Item>
</Grid>
{/* <Grid item xs={4}>
<Item>
quantity
</Item>
</Grid> */}
</Grid>
</Box>
{product.map((element,index)=>(<Product key={index} product={element} />))}
<Box sx={{ width: '100%' }}>
<Grid container rowSpacing={1} columnSpacing={{ xs: 1, sm: 2, md: 3 }} justifyContent="center"
alignItems="center" >
<Grid item xs={4}>
<Item>{tot}</Item>
</Grid>
</Grid>
</Box>
<div>
<Button href='/payment'>pay</Button>
</div>
</div>
)
}
I want to display the product name and price which I have stored in a database . when the program runs it dosen't show product name nor price but when I re run the program it shows the product name and price.I have populated the product hook with a request from the database and pushing the details of all the products to an array of objects called products.when the program is first run it consolelogs the details of the products correctly.but it doesnt render the product name and price.the same thin
this is the backend code.
const express = require('express')
const router = express.Router()
const signUpTemplateCopy = require('../server/models/signupmodel')
const ownersignUpcopy = require('../server/models/ownersignupmodel')
const barcode = require('./models/barcode')
router.post('/signup',async (request,response) => {
const signedUpUser = new signUpTemplateCopy({
firstName : request.body.firstName,
lastName: request.body.lastName,
email: request.body.email,
password: request.body.password,
})
//signedUpUser.save()
const user = await signUpTemplateCopy.findOne({email:request.body.email})
if(!user){
signedUpUser.save()
return response.json({status:"ok"})
}
else{
return response.json({status:"error",error:"user already exist"})
}
})
router.post('/signin',async(request,response)=>{
const user = await signUpTemplateCopy.findOne({email:request.body.email,password:request.body.password})
console.log(user)
if(user){
return response.json({status:"ok"})
}
else{
return response.json({status:"error",error:"invalid login"})
}
})
router.post('/ownersignup',async(request,response) => {
const signedUpUser = new ownersignUpcopy({
firstName : request.body.firstName,
lastName: request.body.lastName,
email: request.body.email,
password: request.body.password,
})
//signedUpUser.save()
const user = await ownersignUpcopy.findOne({email:request.body.email})
if(!user){
signedUpUser.save()
return response.json({status:"ok"})
}
else{
return response.json({status:"error",error:"user already exist"})
}
})
router.post('/ownersignin',async(request,response)=>{
const user = await ownersignUpcopy.findOne({email:request.body.email,password:request.body.password})
console.log(user)
if(user){
return response.json({status:"ok"})
}
else{
return response.json({status:"error",error:"invalid login"})
}}
)
router.post('/barcode',async(request,response)=>{
const product = await barcode.findOne({productid:request.body.productid})
response.json({product})
})
module.exports = router
The component is not rerendering after the deletion of an element in the state but the state does change.
In the component, you can add an element in the array (which is a state) through form, see all the elements in the array, and delete it from the state using the button. So after deleting an element that is in the state, the component does not rerender. Following is the code of the component:
import React, { useEffect, useState } from 'react';
import {
Typography,
IconButton,
Button,
TextField,
Paper,
} from '#mui/material';
import {
CancelOutlined,
AddBoxOutlined,
VisibilityOutlined,
VisibilityOffOutlined,
} from '#mui/icons-material';
export default function Test1() {
const [subNames, setSubNames] = useState([]);
const [subName, setSubName] = useState('');
const [showSubForm, setShowSubForm] = useState(false);
const onSubNameChange = (e) => {
setSubName(e.target.value);
};
const onSubNameSubmit = () => {
if (!subName) return alert('Enter name!');
setSubNames((prev) => prev.concat({ name: subName }));
setShowSubForm(false);
setSubName('');
};
const subForm = (
<>
<div
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}>
<TextField
label='Sub Todo Name'
onChange={onSubNameChange}
name='subTodoName'
value={subName}
size='small'
variant='outlined'
fullWidth
/>
<IconButton onClick={onSubNameSubmit}>
<AddBoxOutlined color='primary' />
</IconButton>
</div>
<br />
</>
);
const onDelete = (position, e) => {
let arr = subNames;
arr.splice(position, 1);
setSubNames(arr);
};
return (
<div>
<h1>Hello World!</h1>
{subNames.map((item, key) => (
<Paper
key={key}
sx={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
margin: 'auto',
padding: 10,
marginTop: 10,
borderRadius: '10px',
}}
elevation={3}>
<div sx={{ display: 'flex', alignItems: 'center' }}>
<Typography variant='body1'>
<b>Sub Todo-{key + 1}:</b>
</Typography>
<Typography variant='body1'>{item?.name}</Typography>
</div>
<IconButton onClick={(e) => onDelete(key, e)}>
<CancelOutlined color='primary' />
</IconButton>
</Paper>
))}
<br />
{showSubForm && subForm}
<div>
{showSubForm && (
<Button
variant='contained'
sx={{ float: 'right' }}
color='primary'
size='small'
startIcon={<VisibilityOffOutlined />}
onClick={() => setShowSubForm(false)}>
Add sub todo item
</Button>
)}
{!showSubForm && (
<Button
variant='contained'
sx={{ float: 'right' }}
onClick={() => setShowSubForm(true)}
color='primary'
size='small'
startIcon={<VisibilityOutlined />}>
Add sub todo item
</Button>
)}
</div>
</div>
);
}
React won't update the DOM every time you give the same state to a setState. For primitive values like Number, String, and Boolean, it's obvious to know whether we are giving a different value or not.
For referenced values like Object and Array on the other hand, changing their content doesn't flag them as different. It should be a different memory reference. See your commented code to understand what you are doing wrong:
let arr = subNames; // does a reference copy, means arr === subNames
arr.splice(position, 1); // changes its content, but still arr === subNames
setSubNames(arr); // at this point it's like nothing has changed
A solution could be the spread operator, which will create a copy of your existing array but on a new memory reference, like so:
let arr = [...subNames]; // creates a copy of subNames on a new reference
arr.splice(position, 1); // updates the content of the newly created array
setSubNames(arr); // new reference is given to setSubNames
I am learning react.js and trying to implement basic add to cart functionality trying to keep it simple, just to understand the flow.
I have two cards, in one of them are listed the items, and after selecting the item and clicking the button the corresponding item should appear in the another card.
As far as I understand I need to somehow filter and check if the unique property has a match with the parameter, add to the card.
Please, find the Codesandbox link and the code below
import Box from "#mui/material/Box";
import Button from "#mui/material/Button";
import Card from "#mui/material/Card";
import CardContent from "#mui/material/CardContent";
import { styled } from "#mui/material/styles";
import React, { useState, useEffect } from "react";
const StyledBox = styled("div")({
paddingRight: "70px",
paddingLeft: "70px",
boxShadow: "rgba(100, 100, 111, 0.2) 0px 7px 29px 0px",
backgroundColor: "#FFFFFF",
border: "1px solid black",
minHeight: "200px"
});
const AddToCard = () => {
const [data, setData] = useState([]);
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/users")
.then((res) => res.json())
.then((result) => setData(result))
.catch((e) => console.log(e));
}, []);
const addToCardHandleClick = (id) => {
setData(
data.map((item: any) => {
const tempItem = data.find((item: any) => item === id);
if (!tempItem) return [...item, { id }];
})
);
};
return (
<Card sx={{ maxWidth: 700, minHeight: "300px" }}>
<CardContent>
<Box
sx={{
display: "flex",
gap: "30px",
justifyContent: "center",
overflow: "auto"
}}
>
<StyledBox sx={{ minWidth: "100px" }}></StyledBox>//The selected item should appear here after clicking
<Box
sx={{
alignSelf: "center",
gap: "10px"
}}
>
<Button
variant="outlined"
onClick={() => addToCardHandleClick(null)} //instead of null should be correct property
>
Move
</Button>
</Box>
<StyledBox>
{data &&
data.map((item) => (
<ul key={item.id} style={{ listStyle: "none" }}>
<li>{item.name}</li>
</ul>
))}
</StyledBox>
</Box>
</CardContent>
</Card>
);
};
export default AddToCard;
Any help will be appreciated
I try it in the code sandbox: https://codesandbox.io/s/imgmediacard-material-demo-forked-9nq22
I use item.selected to keep track of clicked element and once you click on move I drop the item.selected property and add item.moved = true instead...
If you need to make the moved items appear in the right order, you can either:
Add item.order = ++counter or item.order = Date.now() and sort all item.moved by item.order
Use a separete array for moved items
what my task is I am using a table with two different conditions like in the first table whatever data coming I will show that in the first table and in the second table whatever row I select in the first table that I want to show in the second table as the second table I called select summary so my task is in the first table whatever row I selected in need to how that row in the second table I am using same table component for this for better you can see CodeSandBox link
import React, { useState, useMemo, useEffect } from "react";
import {
Grid,
makeStyles,
CardContent,
Card,
Box
} from "#material-ui/core";
import EnhancedTable from "./EnhancedTable";
const useStyles = makeStyles((theme) => ({
root: {
padding: theme.spacing(0, 2, 2),
},
formGrid: {
padding: theme.spacing(2),
},
cardColor: {
borderColor: "#0bb7a7",
},
}));
function AddToExclusionList() {
const classes = useStyles();
const [sanctionsList, setSanctionsList] = useState([]);
const updateListsRow = ({ index, value, row }, listType) => {
switch (listType) {
case "sanctions":
setSanctionsList((prevState) => {
prevState[index].status = value;
return [...prevState];
});
break;
default:
}
};
return (
<Grid className={classes.root}>
<Grid item xs={12} style={{ textTransform: "capitalize" }}>
<Card className={classes.cardColor} variant="outlined">
<CardContent>
<>
<EnhancedTable
show={true}
step="first"
/>
</>
</CardContent>
</Card>
</Grid>
<Box mt={3}></Box>
<Grid item xs={12} style={{ textTransform: "capitalize" }}>
<Card className={classes.cardColor} variant="outlined">
<CardContent>
<>
<h3>summary table</h3>
<EnhancedTable
checkboxToggle={(rowDetails) => {
updateListsRow(rowDetails, "sanctions");
}}
/>
</>
</CardContent>
</Card>
</Grid>
</Grid>
);
}
export default AddToExclusionList;
CodeSandBox Link
You've achieved your goal very weird! Anyway, based on your code in codesandbox. You need to add a state to AddToExclusionList component, like this:
const [newRows, setNewRows] = useState([]);
const setSummaryRows = (selectedRows) => {
const copy = [...rows];
const filteredRows = copy.filter((x) => selectedRows.includes(x.name));
setNewRows(filteredRows);
};
We need the mentioned state to update the summary table's rows.
Also add rows and setNewRows prop to EnhancedTable and give it rows from out of the component. In addition move rows and createData to App.js. So you should use EnhancedTable in App.js same as bellow:
<Grid className={classes.root}>
<Grid item xs={12} style={{ textTransform: "capitalize" }}>
<Card className={classes.cardColor} variant="outlined">
<CardContent>
<>
<h3>first table</h3>
<EnhancedTable
// passing data for rendering table according condition
step="first"
rows={rows}
setNewRows={(selected) => {
setSummaryRows(selected);
}}
/>
</>
</CardContent>
</Card>
</Grid>
<Box mt={3}></Box>
<Grid item xs={12} style={{ textTransform: "capitalize" }}>
<Card className={classes.cardColor} variant="outlined">
<CardContent>
<>
<h3>summary table</h3>
<EnhancedTable
// trying to pasing selected data
rows={newRows}
setNewRows={() => {}}
/>
</>
</CardContent>
</Card>
</Grid>
</Grid>
And the last part is using useEffect based on selected in EnhancedTable component:
useEffect(() => {
setNewRows(selected);
}, [selected]);