Question about React.useEffect() and React.useState() - javascript

I'm having an issue where useEffect isn't triggering a re-render based on useState changing or useState isn't changing which isn't triggering useEffect. I noticed this issue once I selected an asset that should update useState as the selected component and then I select another its no problem but once I select an asset that has already been selected.. nothing happens? Any suggestions or anything is greatly appreciated!! Thanks!
export default function SingleAsset({svg, name, size = '60', group}) {
const [assetType, setAssetType] = React.useState(null);
const [officeType, setOfficeType] = React.useState(null);
const [industrialType, setIndustrialType] = React.useState(null);
const [financingType, setFinancingType] = React.useState(null);
const [investmentType, setInvestmentType] = React.useState(null)
const acquistionStatus = useSelector(state => state.Acquisition)
const dispatch = useDispatch()
const classes = useStyles()
React.useEffect(() => {
if(financingType === 'Acquisition') {
const data = {financingType}
dispatch(updateFormData(data))
dispatch(toggleAcquisitionOn())
}
if(financingType) {
if(financingType !== 'Acquisition') dispatch(toggleAcquisitionOff())
const data = {financingType}
dispatch(updateFormData(data))
}
if(industrialType) {
const data = {industrialType}
dispatch(updateFormData(data))
}
if(officeType) {
const data = {officeType}
dispatch(updateFormData(data))
}
if(investmentType) {
const data = {investmentType}
dispatch(updateFormData(data))
console.log(data)
}
if(assetType) dispatch(updateAssetData(assetType))
console.log(financingType)
console.log(officeType)
console.log(industrialType)
console.log(investmentType)
},[investmentType,assetType,officeType,industrialType,financingType])
const handleSelect = (group, name) => {
switch(group) {
case 'main':
setAssetType(name)
break
case 'office':
setOfficeType(name)
break
case 'industrial':
setIndustrialType(name)
break
case 'financing':
setFinancingType(name)
break
case 'investment':
setInvestmentType(name)
break
default:
throw new Error('group not found')
}
}
return (
<Grid
className={classes.container}
item
>
<Grid
container
direction="column"
alignItems="center"
>
<IconButton onClick={() => handleSelect(group, name)}>
<img src={svg} color="white" height={size} />
</IconButton>
<Typography
variant="body1"
color="white"
align="center"
>
{name}
</Typography>
</Grid>
</Grid>
)
}

That's actually an expected behavior.
React uses "shallow comparison" (check this other great question for more on that), which essentially means it'll compare the previous and new value with ===. This is the reason one should not mutate state objects. Because of this, when your code tries to update the state to the same value it already has it won't actually do it... it's the same, so no re-render will be triggered.
To solve this, we can force React to update with some clever coding that will make React detect a state change:
// Setup a new state
const [, updateState] = useState();
// Create a function that will update state with a new object
// This works because {} === {} is always false, making React trigger a re-render
const forceUpdate = useCallback(() => updateState({}), []);
Your sample code would be something like this:
export default function SingleAsset({svg, name, size = '60', group}) {
const [, updateState] = useState();
const forceUpdate = useCallback(() => updateState({}), []);
const [assetType, setAssetType] = React.useState(null);
const [officeType, setOfficeType] = React.useState(null);
const [industrialType, setIndustrialType] = React.useState(null);
const [financingType, setFinancingType] = React.useState(null);
const [investmentType, setInvestmentType] = React.useState(null)
const acquistionStatus = useSelector(state => state.Acquisition)
const dispatch = useDispatch()
const classes = useStyles()
React.useEffect(() => {
if(financingType === 'Acquisition') {
const data = {financingType}
dispatch(updateFormData(data))
dispatch(toggleAcquisitionOn())
}
if(financingType) {
if(financingType !== 'Acquisition') dispatch(toggleAcquisitionOff())
const data = {financingType}
dispatch(updateFormData(data))
}
if(industrialType) {
const data = {industrialType}
dispatch(updateFormData(data))
}
if(officeType) {
const data = {officeType}
dispatch(updateFormData(data))
}
if(investmentType) {
const data = {investmentType}
dispatch(updateFormData(data))
console.log(data)
}
if(assetType) dispatch(updateAssetData(assetType))
console.log(financingType)
console.log(officeType)
console.log(industrialType)
console.log(investmentType)
},[investmentType,assetType,officeType,industrialType,financingType])
const handleSelect = (group, name) => {
switch(group) {
case 'main':
setAssetType(name)
break
case 'office':
setOfficeType(name)
break
case 'industrial':
setIndustrialType(name)
break
case 'financing':
setFinancingType(name)
break
case 'investment':
setInvestmentType(name)
break
default:
throw new Error('group not found')
}
}
return (
<Grid
className={classes.container}
item
>
<Grid
container
direction="column"
alignItems="center"
>
<IconButton onClick={() => {
handleSelect(group, name);
forceUpdate(); // <-- this is were the magic happens
}}>
<img src={svg} color="white" height={size} />
</IconButton>
<Typography
variant="body1"
color="white"
align="center"
>
{name}
</Typography>
</Grid>
</Grid>
)
}
Please note forcing a re-render should be a last resort, re-rendering can be a rather expensive operation and should be handled with care.

Related

React component renders twice despite Strict mode being disabled

I have a specific component that renders twice. Basically, I am generating two random numbers and displaying them on the screen and for a split second, it shows undefined for both then shows the actual numbers. To test it further I did a simple console.log and it indeed logs it twice. So my question splits into two - 1. Why does the typography shows undefined for a split second before rendering? 2 - Why does it render twice?
Here's the related code:
Beginnging.js - this a counter that counts down from 3 and fires an RTK action, setting gameStart true.
function Beginning() {
const [count, setCount] = useState(3);
const [message, setMessage] = useState("");
const dispatch = useDispatch();
const countRef = useRef(count);
useEffect(() => {
countRef.current = count;
}, [count]);
const handleCount = () => {
if (countRef.current === 1) {
return setMessage("GO");
}
return setCount((count) => count - 1);
};
useEffect(() => {
const interval = setInterval(() => {
handleCount();
}, 1000);
if (message==='GO') {
setTimeout(() => {
dispatch(start());
}, 1000);
}
return () => clearInterval(interval);
}, [count, message]);
return (
<>
<Typography variant="h1" fontStyle={'Poppins'} fontSize={36}>GET READY...</Typography>
<Typography fontSize={48} >{count}</Typography>
<Typography fontSize={60} >{message}</Typography>
</>
);
}
export default Beginning;
AdditionMain.js - based on gameStart, this is where I render Beginning.js, once it counts down, MainInput is rendered.
const AdditionMain = () => {
const gameStart = useSelector((state) => state.game.isStarted);
const operation = "+";
const calculation = generateNumbersAndResults().addition;
if (!gameStart){
return <Beginning/>
}
return (
<>
<MainInput operation={operation} calculation={calculation} />
</>
);
};
export default AdditionMain;
MainInput.js - the component in question.
const MainInput = ({ operation, calculation }) => {
const [enteredValue, setEnteredValue] = useState("");
const [correctValue, setCorrectValue] = useState(false);
const [calculatedNums, setCalculatedNums] = useState({});
const [isIncorrect, setIsIncorrect] = useState(false);
const [generateNewNumbers, setGenerateNewNumbers] = useState(false);
const [haveToEnterAnswer, setHaveToEnterAnswer] = useState(false);
const dispatch = useDispatch();
const seconds = useSelector((state) => state.game.seconds);
const points = useSelector((state) => state.game.points);
const lives = useSelector((state) => state.game.lives);
const timerValid = seconds > 0;
const newChallenge = () => {
setIsIncorrect(false);
setHaveToEnterAnswer(false);
dispatch(restart());
};
const handleCount = () => {
dispatch(loseTime());
};
useEffect(() => {
let interval;
if (timerValid) {
interval = setInterval(() => {
handleCount();
}, 1000);
}
return () => {
console.log("first");
clearInterval(interval);
};
}, [timerValid]);
useEffect(() => {
setCalculatedNums(calculation());
setGenerateNewNumbers(false);
setCorrectValue(false);
setEnteredValue("");
}, [generateNewNumbers]);
const submitHandler = () => {
if (correctValue) {
setGenerateNewNumbers(true);
dispatch(gainPoints());
dispatch(gainTime());
}
if (+enteredValue === calculatedNums.result) {
setCorrectValue(true);
} else if (enteredValue.length === 0) {
setHaveToEnterAnswer(true);
} else {
setIsIncorrect(true);
dispatch(loseLife());
}
};
const inputValueHandler = (value) => {
setIsIncorrect(false);
setHaveToEnterAnswer(false);
setEnteredValue(value);
};
const submitOrTryNewOne = () => {
return correctValue ? "Try new one" : "Submit";
};
return (
<>
<Navbar />
{console.log("hello")}
{seconds && lives > 0 ? (
<>
<GameInfo></GameInfo>
<Container component="main" maxWidth="xs">
<Box
sx={{
marginTop: 8,
display: "flex",
flexDirection: "column",
alignItems: "center",
}}
>
<Typography>
Fill in the box to make the equation true.
</Typography>
<Typography fontSize={28}>
{operation !== "/"
? `${calculatedNums.number1} ${operation} ${calculatedNums.number2}`
: `${calculatedNums.number2} ${operation} ${calculatedNums.number1}`}{" "}
=
</Typography>
<TextField
inputProps={{ inputMode: "numeric", pattern: "[0-9]*" }}
type="number"
name="sum"
id="outlined-basic"
label=""
variant="outlined"
onChange={(event) => {
inputValueHandler(event.target.value);
}}
disabled={correctValue}
value={enteredValue}
></TextField>
{haveToEnterAnswer && enterAnswer}
{correctValue && correctAnswer}
{isIncorrect && wrongAnswer}
<Button
type="button"
sx={{ marginTop: 1 }}
onClick={() => submitHandler()}
variant="outlined"
>
{isIncorrect ? "Try again!" : submitOrTryNewOne()}
</Button>
</Box>
</Container>
</>
) : (
<>
<Typography>GAME OVER</Typography>
<Typography>Final Score: {points}</Typography>
<Button onClick={newChallenge}>New Challenge</Button>
</>
)}
</>
);
};
export default MainInput;
PS: I'm trying to figure out how to get this running on Codesandbox
The problem is that you render the page before the calculaed nums are established.
Try this instead. It will prevent the element from displaying until nums are generated.
const [calculatedNums, setCalculatedNums] = useState(null);
// This is down in your render area. Only render once calculatedNums are non-null.
{ calculatedNums &&
<Typography fontSize={28}>
operation !== "/"
? `${calculatedNums.number1} ${operation} ${calculatedNums.number2}`
: `${calculatedNums.number2} ${operation} ${calculatedNums.number1}`}{" "}
=
</Typography>
}
In addition, you are probably generating your numbers twice, because you will generate new numbers on initial load, but then when you hit setGenerateNewNumbers(true); it will trigger a new calculation, which will then set the generatedNewNumbers to false, which will call the calc again, since it triggers whenever that state changes. It stops after that because it tries to set itself to false again and doesn't change.
You are changing a dependency value within the hook itself, causing it to run again. Without looking a lot more into your program flow, a really hacky way of fixing that would just be to wrap your useEffect operation inside an if check:
useEffect(()=>{
if (generateNewNumbers === true) {
//All your stuff here.
}
}, [generateNewNumbers]
That way, it won't run again when you set it to false.

Component returning nested React Elements not displaying

I have a default component Collection which uses a sub-component called RenderCollectionPieces to display UI elements. I can't figure out why I am able to see the data for image.name in the console but not able to see the UI elements display.
Additional information:
There are no errors in the console
If I replace <p>{image.name}</p> with <p>TESTING</p>, still nothing shows.
columnOrg is a list of lists
each list in columnOrg is a list of maps with some attributes
Index.js:
const RenderCollectionPieces = () => {
const {collectionId} = useParams();
let listOfImageObjects = collectionMap[collectionId];
let imagesPerColumn = Math.ceil(listOfImageObjects.length / 4);
let columnOrg = [];
while (columnOrg.length < 4){
if(imagesPerColumn > listOfImageObjects.length){
imagesPerColumn = listOfImageObjects.length;
}
columnOrg.push(listOfImageObjects.splice(0,imagesPerColumn))
}
let collectionList = columnOrg.map((col) => {
return(
<Grid item sm={3}>
{
col.map((image) => {
console.log(image.name)
return(
<p>{image.name}</p>
)
})
}
</Grid>
)
});
return collectionList;
};
const Collection = ({ match }) => {
const {collectionId} = useParams();
return(
<Box sx={{ background:'white'}}>
<Grid container>
<RenderCollectionPieces />
</Grid>
</Box>
)
};
export default Collection;
I think you are misunderstanding state management in React. Every variable you want to remember inbetween component re-renders should be included in state using useState hook. If you want to perform something initially like your while loop, use it inside useEffect hook.
const MyComponent = () => {
const [myCounter, setMyCounter] = useState(0);
useEffect(() => {
console.log("This will be performed at the start");
}, []);
return (
<Fragment>
<button onClick={() => setMyCounter(myCounter++)} />
You clicked {myCounter} times
</Fragment>
)
}
If you are unfamiliar with useState and useEffect hooks I recommend learning about them first to understand how React manages state and re-renders: https://reactjs.org/docs/hooks-intro.html
Got it to work by using useEffect/useState as recommended by Samuel Oleksak
const RenderCollectionPieces = (props) => {
const [columnOrg, setColumnOrg] = useState([]);
useEffect(() => {
let columnSetup = []
let listOfImageObjects = collectionMap[props.collectionId.collectionId];
let imagesPerColumn = Math.ceil(listOfImageObjects.length / 4);
while (columnSetup.length < 4){
if(imagesPerColumn > listOfImageObjects.length){
imagesPerColumn = listOfImageObjects.length;
}
columnSetup.push(listOfImageObjects.splice(0,imagesPerColumn))
}
setColumnOrg(columnSetup);
},[]);
return (
columnOrg.map((column) => {
return (
<Grid item sm={3}>
{
column.map((image) => {
return (<img src={image.src} alt={image.name}/>)
})
}
</Grid>
)
})
)
};

React-native application hanging when updating a parent from a child

I've a weird behavior here.
I'm trying to update a parent component from a child.
I've thus something like this for the child:
const LabelList = ({editable, boardLabels, cardLabels, size='normal', udpateCardLabelsHandler}) => {
return (
<DropDownPicker
labelStyle={{
fontWeight: "bold"
}}
badgeColors={badgeColors}
showBadgeDot={false}
items={items}
multiple={true}
open={open}
onChangeValue={(value) => udpateCardLabelsHandler(value)}
value={value}
setOpen={setOpen}
setValue={setValue} />
)
}
And, for the parent, something like this:
const CardDetails = () => {
const [updatedCardLabels, setUpdatedCardLabels] = useState([])
const [card, setCard] = useState({})
const [editMode, setEditMode] = useState(false)
// Handler to let the LabelList child update the card's labels
const udpateCardLabelsHandler = (values) => {
const boardLabels = boards.value[route.params.boardId].labels
const labels = boardLabels.filter(label => {
return values.indexOf(label.id) !== -1
})
console.log('updated labels',labels)
setUpdatedCardLabels(labels)
}
return (
<View style={{zIndex: 10000}}>
<Text h1 h1Style={theme.title}>
{i18n.t('labels')}
</Text>
<LabelList
editable = {editMode}
boardLabels = {boards.value[route.params.boardId].labels}
cardLabels = {card.labels}
udpateCardLabelsHandler = {udpateCardLabelsHandler} />
</View>
)
And, this just doesn't work: As soon as I try changing something in the DropDownPicker the application hangs. The console.log statement isn't even executed and no errors show up in my expo console.
What's strange is that if I change the updateCardLabels state to be a boolean for example, everything works ok (eg: the console.log statement is executed):
const [updatedCardLabels, setUpdatedCardLabels] = useState(false)
// Handler to let the LabelList child update the card's labels
const udpateCardLabelsHandler = (values) => {
const boardLabels = boards.value[route.params.boardId].labels
const labels = boardLabels.filter(label => {
return values.indexOf(label.id) !== -1
})
console.log('updated labels',labels)
setUpdatedCardLabels(true)
}
Please note that updatedCardLabels isn't used anywhere: it's a dummy variable that I'm just using to debug this issue (to make sure I was not ending in some endless render loop or something similar).
For the sake of completeness, here's what labels looks like at line console.log('updated labels',labels) (please not that I can only see this value when doing setUpdatedCardLabels(true) as otherwise, when the code does setUpdatedCardLabels(labels), the console.log statement is not executed, as mentioned earlier):
updated labels Array [
Object {
"ETag": "a95b2566521a73c5edfb7b8f215948bf",
"boardId": 1,
"cardId": null,
"color": "CC317C",
"id": 9,
"lastModified": 1621108392,
"title": "test-label",
},
]
Does anybody have an explanation for this strange behavior?
Best regards,
Cyrille
So, I've found the problem: It was a side effect of the DrowpDownPicker.
I've solved it by changing my child as follow:
const LabelList = ({editable, boardLabels, cardLabels, size='normal', udpateCardLabelsHandler}) => {
const [open, setOpen] = useState(false);
const [value, setValue] = useState(cardLabels.map(item => item.id));
const theme = useSelector(state => state.theme)
// Updates parent when value changes
useEffect(() => {
if (typeof udpateCardLabelsHandler !== 'undefined') {
udpateCardLabelsHandler(value)
}
}, [value])
return (
<DropDownPicker
labelStyle={{
fontWeight: "bold"
}}
badgeColors={badgeColors}
showBadgeDot={false}
items={items}
multiple={true}
open={open}
value={value}
setOpen={setOpen}
setValue={setValue} />
)

change in one item re runs whole map component loop in ReacJS

I am dispatching an add comment action on a specific post re runs component loop again instead of updating a specific one. Suppose, If I have 100 posts adding comments to one post runs the component loop again and iterates again 100 times. Is there is any way to re-render only a specific item instead of running the whole component loop again?
Here's my code of the looped component
const Post = ({totalComments, like, _id, image, caption}) => {
const {enqueueSnackbar} = useSnackbar();
const dispatch = useDispatch();
const { user } = useSelector((state) => state.loadUser);
const { loading: loadingComments, comments } = useSelector((state) => state.listComments);
const { success: addCommentSucess, error: addCommentError } = useSelector((state) => state.addComment);
const [openComment, setOpenComment] = useState('');
const [commentsLength, setCommentsLength] = useState(totalComments);
//listing of comments
useEffect(() => {
if (addCommentError) {
enqueueSnackbar(addCommentError, {variant: 'error'});
dispatch(clearErrors());
}
}, [addCommentError, addCommentSucess, dispatch, enqueueSnackbar]);
const openCommentHandler = () => {
dispatch(listComments(_id));
setOpenComment(!openComment);
}
const addCommentHandler = (data) => {
console.log('addcomehandle')
dispatch(addComment(data));
setCommentsLength(commentsLength+1);
}
return (
<Card className='post-container'>
<div className='button-wrapper'>
<IconButton onClick={openCommentHandler} color={openComment ? 'primary' : 'default'}>
<SvgIcon component={CommentOutlinedIcon} />
</IconButton>
<p>{commentsLength}</p>
</div>
{!loadingComments && openComment && (
<Comments comments={comments} />
)}
<Divider />
<AddComment onAddComment={addCommentHandler} postId={_id} />
</Card>
);
};
export default Post;

Pass data from API to another component with TypeScript and ReactJS

i'am learning TS yet and I trying to create an application where I get data from API, show results and if someone click on item, it shows a modal with more details, but i'am trouble cause basically my component doesn't render... Look at my code =) !
import IMovie from "../../models/movie.model";
import Modal from "../modal/Modal";
import "./style";
import {
ResultsBody,
ResultsContainer,
TitleResult,
MovieStats,
MovieCover,
MovieStatsDescription,
} from "./style";
interface ISearch {
search?: string;
}
const URL =
"#";
const Results = ({ search }: ISearch) => {
const [data, setData] = React.useState<IMovie[]>([]);
const [currentPage, setCurrentPage] = React.useState(1);
const [dataPerPage] = React.useState(10);
async function getData() {
const response: AxiosResponse<any> = await axios.get(URL);
setData(response.data.results);
}
React.useEffect(() => {
getData();
}, []);
const indexLastData = currentPage * dataPerPage;
const indexFirstData = indexLastData - dataPerPage;
const currentData = data.slice(indexFirstData, indexLastData);
const paginate = (pageNumber: number) => setCurrentPage(pageNumber);
const filteredData = data.filter((results) => {
return results.title.toLowerCase().includes(search!.toLocaleLowerCase());
});
return (
<>
<ResultsContainer>
<TitleResult>
<span>Personagem</span>
<span>Sinopse</span>
<span>Data</span>
</TitleResult>
{!search
? currentData.map((item) => (
<ResultsBody
key={item.id}
// onClick={() => {
// selectedMovie(item);
// }}
>
<MovieCover
src={`https://image.tmdb.org/t/p/w185${item.poster_path}`}
alt="poster"
/>
<MovieStats style={{ fontWeight: `bold` }}>
{item.title}
</MovieStats>
<MovieStatsDescription>{item.overview}</MovieStatsDescription>
<MovieStats>{item.release_date}</MovieStats>
</ResultsBody>
))
: filteredData.map((item) => (
<ResultsBody key={item.id}>
<MovieCover
src={`https://image.tmdb.org/t/p/w185${item.poster_path}`}
alt="poster"
/>
<MovieStats style={{ fontWeight: `bold` }}>
{item.title}
</MovieStats>
<MovieStatsDescription>{item.overview}</MovieStatsDescription>
<MovieStats>{item.release_date}</MovieStats>
</ResultsBody>
))}
</ResultsContainer>
<Modal data={data} /> //HERE IS WHERE I'AM CALLING MY MODAL, I want to pass data here
<Pagination
dataPerPage={dataPerPage}
totalData={data.length}
paginate={paginate}
currentPage={currentPage}
/>
</>
);
};
export default Results;
This is my MODAL component
import React from "react";
import { ModalContainer } from "./style";
import IMovie from "../../models/movie.model";
interface IData {
data: IMovie[];
}
const Modal = ({ data }: IData) => {
console.log(data);
return <ModalContainer>{data.title}</ModalContainer>; //HERE IS NOT WORKING
};
export default Modal;
As you can see guys, I can show all results on console.log, but when I put inside the return the log says ''TypeError: Cannot read property 'title' of undefined''
If someone could help me I'd really appreciate! Thanks a lot =)
Movie vs Array
You are getting the error
'Property 'title' does not exist on type 'IMovie[]'. TS2339
in your Modal component because data is an array of movies. An array doesn't have a title property.
You want the modal to show one movie, so you should only pass it one movie.
interface IData {
data: IMovie;
}
Current Selection
Changing the IData interface fixes the issues in Modal, but creates a new error in Results because we are still passing an array. The correct prop is the data for the movie that was clicked. What movie is that? We need to use a useState hook in order to store that data.
Depending on where you control the open/closed state of the Modal, you may also want to pass an onClose callback that clears the selected movie state.
the state:
const [selected, setSelected] = React.useState<IMovie | null>(null); // is a movie or null
in the movie:
onClick={() => setSelected(item)}
the modal:
{selected === null || (
<Modal
data={selected}
onClose={() => setSelected(null)}
/>
)}
Avoid Duplicated Code Blocks
You are rendering a movie the same way whether it's from currentData or filteredData, so we want to combine those. We could create a shared renderMovie callback or ResultsMovie component to use in both loops, but I think we can actually handle it higher up and just have one loop.
You also want your pagination to reflect the pages of just the matching movies when we are filtering based on a search.
// the matchingMovies is a filtered array when there is a search, or otherwise the entire array
const matchingMovies = search
? data.filter((result) =>
result.title.toLowerCase().includes(search.toLowerCase())
)
: data;
const indexLastData = currentPage * dataPerPage;
const indexFirstData = indexLastData - dataPerPage;
const paginate = (pageNumber: number) => setCurrentPage(pageNumber);
// total for the pagination should be based on matchingMovies instead of data
const totalData = matchingMovies.length;
// make the currentData from the matchingMovies
const currentData = matchingMovies.slice(indexFirstData, indexLastData);
There might be some bugs or potential additional improvements but I can't actually run this without your components :)
const Results = ({ search }: ISearch) => {
const [data, setData] = React.useState<IMovie[]>([]);
const [currentPage, setCurrentPage] = React.useState(1);
const [dataPerPage] = React.useState(10);
const [selected, setSelected] = React.useState<IMovie | null>(null); // is a movie or null
async function getData() {
const response: AxiosResponse<any> = await axios.get(URL);
setData(response.data.results);
}
React.useEffect(() => {
getData();
}, []);
// the matchingMovies is a filtered array when there is a search, or otherwise the entire array
const matchingMovies = search
? data.filter((result) =>
result.title.toLowerCase().includes(search.toLowerCase())
)
: data;
const indexLastData = currentPage * dataPerPage;
const indexFirstData = indexLastData - dataPerPage;
const paginate = (pageNumber: number) => setCurrentPage(pageNumber);
// make the currentData from the matchingMovies
const currentData = matchingMovies.slice(indexFirstData, indexLastData);
return (
<>
<ResultsContainer>
<TitleResult>
<span>Personagem</span>
<span>Sinopse</span>
<span>Data</span>
</TitleResult>
{currentData.map((item) => (
<ResultsBody key={item.id} onClick={() => setSelected(item)}>
<MovieCover
src={`https://image.tmdb.org/t/p/w185${item.poster_path}`}
alt="poster"
/>
<MovieStats style={{ fontWeight: `bold` }}>{item.title}</MovieStats>
<MovieStatsDescription>{item.overview}</MovieStatsDescription>
<MovieStats>{item.release_date}</MovieStats>
</ResultsBody>
))}
</ResultsContainer>
{selected === null || (
<Modal data={selected} onClose={() => setSelected(null)} />
)}
<Pagination
dataPerPage={dataPerPage}
totalData={matchingMovies.length}
paginate={paginate}
currentPage={currentPage}
/>
</>
);
};
interface ModalProps {
data: IMovie;
onClose: () => void;
}
const Modal = ({ data, onClose }: ModalProps) => {
console.log(data);
return <ModalContainer>{data.title}</ModalContainer>;
};

Categories