I am getting an array of 5k objects from jsonplaceholder (photos endpoint). And I want to sort it by albumId using a Material-UI select. So the problem is state is not being displayed properly. When I choose 'desc' options my items grid is not being updated, but when I drop the sorting state to it's initial value, the rerender happens and I am getting a sorted by 'desc' order list.
API link: json placeholder photos JSON
const handleChangeSort = (e: SelectChangeEvent) => {
const sort = e.target.value;
setSort(sort);
};
const handleClearSort = () => {
setSort('');
};
React.useEffect(() => {
if (sort) {
const sortedImages = filteredImages.sort(
byValue((i) => i.albumId, byNumber({ desc: sort === 'desc' }))
);
setFilteredImages(sortedImages);
} else {
setFilteredImages(images);
}
setPage(1);
/* eslint-disable-next-line */
}, [sort]);
React.useEffect(() => {
console.log('NEW FILTERED IMAGES', filteredImages);
}, [filteredImages]);
<>
{!imagesLoading &&
(filteredImages.length ? (
<Grid container item spacing={5} xs={12}>
{filteredImages
.slice(itemsOnPage * (page - 1), page * itemsOnPage)
.map((image: Image) => (
<Grid item xs={4} key={image.id}>
<Card>
<CardHeader title={image.title.slice(0, 20)} />
<CardMedia
component="img"
height="100%"
image={image.thumbnailUrl}
alt={image.title}
/>
<CardActions>
<Button
size="small"
className={classNames(classes.btn, classes.deleteBtn)}
>
<DeleteIcon />
</Button>
<Button
size="small"
className={classNames(classes.btn, classes.previewBtn)}
>
<PreviewIcon />
</Button>
</CardActions>
</Card>
</Grid>
))}
</Grid>
) : (
<Grid item>
<Typography variant="body1">No images were found</Typography>
</Grid>
))}
</>
Furthermore, the useEffect, that is listening for filteredImages to change is not working. I am not getting the console.log after filteredImages change it's value.
UPD: Default sorting by Array.sort is not working either
you can use orderBy lodash for sorting array, you can also check this answer : answer
In your case, you can write useEffect method Like this
import {orderBy} from 'lodash'
React.useEffect(() => {
if (sort) {
setFilteredImages( orderBy(filteredImages, ['albumId'], ['desc']) );
} else {
setFilteredImages(images);
}
setPage(1);
/* eslint-disable-next-line */
}, [sort]);
Related
I want to set all TextField to the initial state after adding data to the table by clicking ADD button
I am able to set that to the initial state by using AssignSearchesForm.resetForm(); but what happens here if I set this in add button click it will reset data but my table data also get removed because it's updated Formik values setTeamData([values, ...teamdata]);
I am adding Formik value in this state after onClick it will get added but if I reset form and set initial this state value also getting set empty so what I want after adding data into table TextField get reset or initial but table data will stay the same either if we do not update that
This way I am trying
<Button
onClick={() => {
AssignSearchesForm.handleSubmit();
// I tried this two way
//first way
// AssignSearchesForm.resetForm();
//second way
// AssignSearchesForm.setFieldValue("selectName","")
// AssignSearchesForm.setFieldValue("selectAge","")
// AssignSearchesForm.setFieldValue("location","")
}}
variant="contained"
>
Add
</Button>
export default function App() {
const [teamdata, setTeamData] = React.useState([]);
const AssignSearchesForm = useFormik({
initialValues: {
selectName: "",
selectAge: "",
location: ""
},
validationSchema,
onSubmit: (values) => {
setTeamData([values, ...teamdata]);
}
});
let filteredArray = nameList.filter(
(e) => !teamdata.some((data) => data.selectName === e.selectName)
);
const handleChange = (e) => {
const selectedName = e.target.value;
const name = nameList.find((data) => data.selectName === selectedName);
const newOptions = Object.values(name).reduce((optionList, key) => {
optionList.push({ value: key, label: key });
return optionList;
}, []);
AssignSearchesForm.setFieldValue("selectName", selectedName);
AssignSearchesForm.setFieldValue("selectAge", newOptions[1]?.value || "");
AssignSearchesForm.setFieldValue("location", newOptions[2]?.value || "");
};
return (
<div className="App">
<Grid container direction="row" spacing={1}>
<Grid item xs={4}>
<TextField
sx={{ minWidth: 150 }}
select
id="outlined-basic"
label="Select Name"
name="selectName"
size="small"
onChange={handleChange}
value={AssignSearchesForm.values.selectName}
error={
AssignSearchesForm.errors.selectName &&
AssignSearchesForm.touched.selectName
}
>
{filteredArray?.map((option) => (
<MenuItem key={option.selectName} value={option.selectName}>
{option.selectName}
</MenuItem>
))}
</TextField>
</Grid>
<Grid item xs={4}>
<TextField
id="outlined-basic"
label="location"
name="location"
size="small"
{...AssignSearchesForm.getFieldProps("location")}
/>
</Grid>
<Grid item xs={4}>
<TextField
id="outlined-basic"
label="Select Age"
name="selectAge"
size="small"
{...AssignSearchesForm.getFieldProps("selectAge")}
/>
</Grid>
<Grid item xs={4}>
<Button
onClick={() => {
AssignSearchesForm.handleSubmit();
// AssignSearchesForm.resetForm();
// AssignSearchesForm.setFieldValue("selectName","")
// AssignSearchesForm.setFieldValue("selectAge","")
// AssignSearchesForm.setFieldValue("location","")
}}
variant="contained"
>
Add
</Button>
</Grid>
</Grid>
<Table teamdata={teamdata} />
</div>
);
}
You can leverage the second parameter formikHelper in onSubmit function of formik.
Better approach pointed out by dev-developer in comments:
onSubmit: (values, formikHelper) => {
setTeamData([values, ...teamdata]);
formikHelper.resetForm();
}
Another alternative approach if resetForm() is not useful.
onSubmit: (values, formikHelper) => {
setTeamData([values, ...teamdata]);
formikHelper.setFieldValue("selectName", "");
formikHelper.setFieldValue("selectAge", "");
formikHelper.setFieldValue("location", "");
}
Here is the modified code sandbox
I'm trying to bind to a nested array to text inputs from a form. There are 6 blocks and each block contains 3 values. I have initially populated the array for mapping by using:
new Array(6).fill(['','',''])
and rendering the values using 2 loops. One for the 6 blocks and the other 3 values inside each block. The 3 values are the ones binding to the form. I refer to each of the inputs using the second parameter of the map function which is the index.
Here is the code in its entirety.
import {
Card,
CardContent,
CardHeader,
CardMedia,
TextField,
Grid,
Typography,
Button,
} from "#material-ui/core";
import axios from "axios";
import { useState } from "react";
import { useHistory } from "react-router";
export default function CreateEditQuestion() {
const answerPack = new Array(6).fill(["", "", ""]);
const [question, setQuestion] = useState("");
const [answers, setAnswer] = useState(answerPack);
const [TimeAllowed, setTimeAllowed] = useState(30);
const [Score, setScore] = useState(1);
const [Date, setDate] = useState("");
const history = useHistory();
const handleAnswerSet = (value, answerIndex, answerTextIndex) => {
var updatedAnswer = answers;
updatedAnswer[answerIndex][answerTextIndex] = value;
setAnswer(updatedAnswer);
};
const handleSubmit = () => {
let data = {
question,
answer_set: answers,
time_allowed: TimeAllowed,
score_value: Score,
date: Date,
};
for (const [key, value] of Object.entries({ question, Date })) {
if (value.trim().length == 0) {
alert(`${key} has not been filled in`);
return false;
}
}
axios
.post(
"https://localhost:8000/question",
data
)
.then((resp) => {
alert("Succesfully added Question");
history.push("/question");
})
.catch((err) => {
console.log(err);
});
};
return (
<>
<h1>Create Question</h1>
<Card elevation={1}>
<CardHeader></CardHeader>
<CardContent>
<div>
<TextField
fullWidth
label="Question"
variant="outlined"
onChange={(e) => {
setQuestion(e.target.value);
}}
></TextField>
</div>
<Grid container direction={"row"} spacing={4}>
{answers.map((answerTexts, i) => {
return (
<Grid key={i} item md={4} width={50}>
{answerTexts.map((text, j) => {
return (
<div style={{ width: "70%", marginTop: "30px" }}>
<TextField
fullWidth
label={`Answer ${i + 1} - ${j + 1}`}
onChange={(ev) => {
handleAnswerSet(ev.target.value, i, j);
}}
variant="outlined"
/>
<br />
</div>
);
})}
</Grid>
);
})}
</Grid>
<Grid container direction={"row"} spacing={4}>
<Grid item md={5} width={80}>
<Typography variant="h6" gutterBottom gutterTop>
Extra Options
</Typography>
<TextField
label={"Time Allowed : "}
variant="outlined"
defaultValue={TimeAllowed}
onChange={(ev) => {
setTimeAllowed(ev.target.value);
}}
/>{" "}
<TextField
label="Score"
variant="outlined"
defaultValue={Score}
onChange={(ev) => {
setScore(ev.target.value);
}}
/>
</Grid>
<Grid item md={5} width={100}>
<Typography variant="h6" gutterBottom gutterTop>
Question Date
</Typography>
<TextField
type="date"
onChange={(ev) => {
setDate(ev.target.value);
}}
/>
</Grid>
</Grid>
<div align="center">
<Button
onClick={() => {
handleSubmit();
}}
variant="contained"
color="primary"
>
Submit Question
</Button>
</div>
</CardContent>
</Card>
</>
);
}
Problem:
On each block, changing any value also changes all the other corresponding inputs in other blocks, so if the first input on the first block is changed, then all other first inputs in the other blocks also get changed,e.g(changing answer 1-1 also changes 2-1,3-1,4-1, etc). I could not trace why. Only the corresponding values should be changed
This is the function responsible for setting the values.
const handleAnswerSet = (value, answerIndex, answerTextIndex) => {
var updatedAnswer = answers;
updatedAnswer[answerIndex][answerTextIndex] = value;
setAnswer(updatedAnswer);
};
I have an array of object which contains images:
import React, { Fragment } from "react"
import { useStaticQuery, graphql } from "gatsby"
import Button from "#material-ui/core/Button"
import Card from "#material-ui/core/Card"
import CardActions from "#material-ui/core/CardActions"
import CardContent from "#material-ui/core/CardContent"
import CardMedia from "#material-ui/core/CardMedia"
import { CardActionArea, GridList } from "#material-ui/core"
import CssBaseline from "#material-ui/core/CssBaseline"
import Grid from "#material-ui/core/Grid"
import Toolbar from "#material-ui/core/Toolbar"
import Typography from "#material-ui/core/Typography"
import Container from "#material-ui/core/Container"
import Link from "#material-ui/core/Link"
import homePageStyles from "../ui/styles/homePageStyles"
import mobile from "../../images/mobileApps.jpeg"
import coporation from "../../images/coporations.png"
import statute from "../../images/gavel.png"
import policy from "../../images/policies.png"
export default function HeroBlock() {
const classes = homePageStyles()
const data = useStaticQuery(graphql`
query getCardCategoriesAndCounts {
allStrapiCategory {
edges {
node {
category
strapiId
}
}
}
allStrapiMobileApplication {
totalCount
}
allStrapiCorporation {
totalCount
}
allStrapiStatutoryLaw {
totalCount
}
allStrapiPolicyIssue {
totalCount
}
}
`)
const categories = data.allStrapiCategory.edges
const categoryImages = [
{ cImg: mobile },
{ cImg: coporation },
{ cImg: statute },
{ cImg: policy },
]
console.log(categoryImages)
const catImage = categoryImages.map(categoryImage => {
return (
<Fragment>
<CardMedia
image={categoryImage.cImg}
className={classes.images}
></CardMedia>
</Fragment>
)
})
const cards = (
<Fragment>
{categories.map(c => (
<Card style={{ width: "15.625rem", wordWrap: "break-word" }}>
<div>{catImage}</div>
<CardActionArea>
<CardContent>
<Typography variant={"h6"}>
{c.node.category.toUpperCase().replace(/_/g, " ")}
</Typography>
<Fragment>
{c.node.category === "Mobile_Apps" ? (
<Typography variant="paragraph">
{`There are currently ${data.allStrapiMobileApplication.totalCount} mobile applications.`}
</Typography>
) : c.node.category === "Corporations" ? (
<Typography variant="paragraph">
{`There are currently ${data.allStrapiCorporation.totalCount} coporations.`}
</Typography>
) : c.node.category === "Statutes" ? (
<Typography variant="paragraph">
{`There are currently ${data.allStrapiStatutoryLaw.totalCount} statutory laws.`}
</Typography>
) : c.node.category === "Policy_Issues" ? (
<Typography variant="paragraph">
{`There are currently ${data.allStrapiPolicyIssue.totalCount} policy issues.`}
</Typography>
) : (
<div></div>
)}
</Fragment>
</CardContent>
</CardActionArea>
</Card>
))}
</Fragment>
)
return (
<React.Fragment>
<CssBaseline />
<main>
<div className={classes.heroContent}>
<Grid container>
<Grid item classes={{ root: classes.heroColumn }}>
<Grid container>
<Grid
item
container
direction="column"
classes={{ root: classes.hero }}
>
<Typography component="h1" variant="h2" align="center">
MIKE <br /> The Mobile Information Knowledge Ecosystem
</Typography>
<Typography variant="h5" align="center" paragraph>
<strong>
<em>The</em>{" "}
</strong>
central repository for how to extract data from a mobile
device, extract data from a mobile application, what data
the mobile app provider retains, and the specific laws
pertaining to accessing that data.
</Typography>
</Grid>
</Grid>
</Grid>
</Grid>
</div>
<Grid Container>
<Grid item container justifyContent="space-evenly">
{cards}
</Grid>
</Grid>
</main>
</React.Fragment>
)
}
Updated with the entire code base. There is multiple map statements. If that is the issue how do you map multiple arrays inside React? Does that require using some sort of data store? It is returning each one of the images 4 times, rather than each images per card.
You generate an array of card media components, and then render this within another loop, so this is why you see the 4 category images repeated for each category mapped later.
const catImage = categoryImages.map(categoryImage => { // array
return (
<Fragment>
<CardMedia
image={categoryImage.cImg}
className={classes.images}
></CardMedia>
</Fragment>
)
})
const cards = (
<Fragment>
{categories.map(c => (
<Card style={{ width: "15.625rem", wordWrap: "break-word" }}>
<div>{catImage}</div> // <-- render array of images here!!
<CardActionArea>
...
Assuming the categoryImages and categories arrays have the same length and sorted order, then you can render the card media component directly in the mapping and use the current index to access the correct image from the categoryImages array.
const cards = (
<Fragment>
{categories.map((c, index) => ( // <-- use current index
<Card style={{ width: "15.625rem", wordWrap: "break-word" }}>
<CardMedia
image={categoryImages[index].cImg} // <-- access by index
className={classes.images}
/>
<CardActionArea>
...
Array.map returns array. So catImage is an array that contains components.You can use each CardMedia component by indicating index number.
catImage[index]
catImage[0] is the first card.
So you can update your code like below:
const cards = ( // <==== line 74
<Fragment>
{categories.map((c, index) => (
<Card style={{ width: "15.625rem", wordWrap: "break-word" }}>
<div>{catImage[index]}</div>
I hope my answer will be solve your problem.
Thanks.
So, I need to make as much dynamic solution as possible for this issue.
I have two states (strings), that needs to be values of an object.
Then I have to push that object into and array and set state with that array as new value.
I have managed do do it, but something is wrong, every time I add an object to the array with spread operator, the state is first empty, and then starts to add previous value, not the one inserted at that moment.
export default function EqtConnect() {
let allSizes = [];
const [eqt_ID, setEqtID] = useState('');
const [eqtSizes, setEqtSizes] = useState([]);
const [sizeID, setSizeID] = useState(null);
const [sizeAmout, setSizeAmont] = useState(null);
/**
* Token
*/
const { token } = useToken();
/**
* Styles
*/
const classes = useStyles();
/**
* handleSubmit function handle submit to API
* #param {event object} e
*/
const handleSubmit = (e) => {
e.preventDefault();
console.log('Na submit ', eqtSizes)
sendData({
eqt_ID,
eqtSizes,
token,
});
}
const addSize = (el) => {
console.log(sizeID, sizeAmout); // Console logs good output
setEqtSizes([...eqtSizes, { 'equipment_size_id': sizeID, 'quantity': sizeAmout }]);
// First time output is [ { 'equipment_size_id': undefined, 'quantity': undefined }]
// Second time output is [ { 'equipment_size_id': '--- previous value ---, 'quantity': --- previous value --- }]
console.log('State of array', eqtSizes);
}
return (
<>
<Grid container spacing={3}>
<Grid item xs={12}>
<Paper className={classes.paper}>
<h1>Equipment</h1>
<Typography>
Connect Data
</Typography>
</Paper>
</Grid>
<Grid item xs={12} sm={12}>
<form action="" onSubmit={(e) => handleSubmit(e)} id="send-image">
<Paper className={classes.paper}>
<Grid container spacing={3}>
<Grid item xs={12} sm={12}>
<TextField id="bikePrice" name="price" label="ID Opreme" onChange={(e) => setEqtID(e.target.value)} />
</Grid>
<Grid item xs={12} sm={12}>
<Box className="input-holder">
<input type="number" name="sizeID" id="sizeID" onChange={e => setSizeID(parseInt(e.target.value))} />
<input type="number" placeholder="Kolicina" onChange={e => setSizeAmont(parseInt(e.target.value))} />
<span className="add-button" onClick={(e) => addSize(e.target)} >+</span>
</Box>
</Grid>
</Grid>
</Paper>
<br />
<Grid item xs={12} sm={12}>
<Button type="submit" variant="contained" color="primary">
Update
</Button>
</Grid>
</form>
</Grid>
</Grid>
</>
)
}
It is not actually guaranteed that the contents of the state variable managed by useState will be updated immediately after calling its setter. eqtSizes will only be brought in sync with the value you passed to setEqtSizes on the next render.
See this FAQ.
So, it is expected that your second log statement will show the stale value.
The state of a value set using React useState hook gets set to the proper value and then reset to null. Critical code below. The click event that sets the startDate to the current date and time is 3 components down from where startDate is initialized. When setStartDate did not work I created an arrow function, updateStartDate. Both had the same problem where the startDate was changed after the click event (witnessed per the console.log in the top component), but was null just before the next click event (per the console.log in the click event). This is not an async problem as I see the change made before subsequent click.
If this is something that just does not work please explain. I could probably fix with useReducer but prefer to keep the useState if there is something I can do to correct this... If not correctable then I would like to at least understand why it does not work so that I can avoid this problem in the future.
export const DisplayTicTacToeContainer = (props) => {
const [startDate, setStartDate]= useState();
const updateStartDate = (newDate) => {
setStartDate(newDate);
}
useEffect (() => {
setStartDate(null);
}, []);
useEffect(() => {
console.log( "displayTicTacToeContainer useEffect for change of startDate = ", startDate)
}, [startDate]);
return (
<DisplayTicTacToeMatch arrayOfMatchingItems ={arrayOfMatchingItems}
startDate={startDate}
setStartDate={setStartDate}
updateStartDate={updateStartDate}
/>);
}
//-----------------------------------------------
export const DisplayTicTacToeMatch = (props) => {
const { startDate,
setStartDate,
updateStartDate,
} = props;
useEffect(() => {
// Performs some prep and working fine.
}, []);
return (
<TicTacToe
startDate={startDate}
setStartDate={setStartDate}
updateStartDate={updateStartDate}
/>
);
}
//-----------------------------------------------
const TicTacToeContainer = (props) => {
const { startDate,
setStartDate,
updateStartDate,
} = props;
const [board, setBoard] = useState(<Board
updateStartDate={updateStartDate}
startDate={startDate}
setStartDate={setStartDate}/>);
return (
<Board/>
)
}
export default TicTacToeContainer;
I renamed the component to BoardComponent and the state variable to boardLayout. I included the full return portion of the BoardComponent below.
As I am still experiencing the problem I would agree with you that, "DisplayTicTacToeContainer is being mounted twice". Any thoughts on how I can avoid this from happening?
Other than this inability to setStartDate, everything is working fine.
//-----------------------------------------------
const Board = (props) => {
const { updateStartDate,
startDate,
setStartDate,
} = props;
return (
<>
<Grid container maxwidth="lg" alignItems="center" spacing={1}>
<Grid item xs={9}>
<Grid container alignItems="center">
<Grid item xs={9}>
<Typography variant = "body1">
First select a square. Once the "Inquiry" word or phrase appears below, find
the correct response in the column on the right and select that buttton. A correct
response will fill the square previously selected with an "O" or "X".
</Typography>
<div style={{ width: '100%' }}>
<Box
display="flex"
flexWrap="wrap"
p={1}
m={1}
bgcolor="background.paper"
css={{ maxWidth: 900 }}
>
<Box p={1} bgcolor="grey.300">
Inquiry : {inquiry}
</Box>
</Box>
<Box
display="flex"
flexWrap="wrap"
p={1}
m={1}
bgcolor="background.paper"
css={{ maxWidth: 900 }}
>
<Box p={1} bgcolor="grey.300">
Next move by : {currentPlayer}
</Box>
<Box p={1} bgcolor="grey.300">
{showStatus}
</Box>
</Box>
</div>
</Grid>
</Grid>
<MyAux>
{boardLayout.map((row, rowId) => {
const columns = row.map((column, columnId) => (
<Grid key={columnId} item>
<ButtonBase >
<Paper
onClick={(e) => {
clickSquareHandler(e);
}}
elevation={4}
data-coord={rowId + ':' + columnId}
id={"Square" + rowId.toString() + columnId.toString()}
className={classes.Paper}>
<Icon
className={classes.Icon}
style={{fontSize: 78}}>
</Icon>
</Paper>
</ButtonBase>
</Grid>
));
return (
<Grid
key={rowId}
className={classes.Grid}
container
spacing={2}>
{columns}
</Grid>)
})}
</MyAux>
</Grid>
<Grid item xs={3} >
<Paper className={classes.paper}>
<Typography variant = "body1">
Response Options
</Typography>
<ButtonGroup
orientation="vertical"
color="secondary"
aria-label="vertical outlined secondary button group"
>
{responseChoices.map((choice) => (
<Controls.Button
key ={choice.value}
text={choice.value}
variant="contained"
color = "secondary"
onClick={() => {
chooseChecker(choice);
}}
className={
response && response.value === choice.value ? "selected" : ""
}
disabled={!!selected[choice.value]}
fullWidth = "true"
size = "small"
/>
))}
</ButtonGroup>
</Paper>
</Grid>
</Grid>
</>
)
}
BoardContainer.propTypes = {
won: PropTypes.func,
size: PropTypes.number
};
export default BoardContainer;
At least, code below doesn't make much sense.
Please don't set state value as a component.
Also, try to name state variable different from components, since it will confuse you at some ppint.
const [board, setBoard] = useState(<Board
updateStartDate={updateStartDate}
startDate={startDate}
setStartDate={setStartDate}/>);
return (
<Board/>
)
Another possibility is that the DisplayTicTacToeContainer is being mounted twice, but I can't confirm it with the code provided.