Cannot make element disappear/appear properly inside of my React App - javascript

So I have built a movie search app.
On the 4th page we have the ability to search for a specific movie or TV show.
Now I have built a logic that will display "Movies(Tv Shows) not found" when there are no search results.
Here is the code of the entire "Search" Component:
const Search = () => {
const [type, setType] = useState(0);
const [page, setPage] = useState(1);
const [searchText, setSearchText] = useState("");
const [content, setContent] = useState([]);
const [numOfPages, setNumOfPages] = useState();
const [noSearchResults, setNoSearchResults] = useState(false);
const fetchSearch = async () => {
try {
const { data } = await axios.get(`https://api.themoviedb.org/3/search/${type ? "tv" : "movie"}?api_key=${process.env.REACT_APP_API_KEY}&language=en-US&query=${searchText}&page=${page}&include_adult=false`);
setContent(data.results);
setNumOfPages(data.total_pages);
} catch (error) {
console.error(error);
}
};
const buttonClick = () => {
fetchSearch().then(() => {
if (searchText && content.length < 1) {
setNoSearchResults(true);
} else {
setNoSearchResults(false);
}
});
};
useEffect(() => {
window.scroll(0, 0);
fetchSearch();
// eslint-disable-next-line
}, [page, type]);
return (
<div>
<div style={{ display: "flex", margin: "25px 0" }}>
<TextField className="textBox" label="Search" variant="outlined" style={{ flex: 1 }} color="secondary" onChange={e => setSearchText(e.target.value)} />
<Button variant="contained" style={{ marginLeft: "10px" }} size="large" onClick={buttonClick}>
<SearchIcon color="secondary" fontSize="large" />
</Button>
</div>
<Tabs
value={type}
indicatorColor="secondary"
onChange={(event, newValue) => {
setPage(1);
setType(newValue);
}}
style={{
marginBottom: "20px",
}}
>
<Tab style={{ width: "50%" }} label="Search Movies" />
<Tab style={{ width: "50%" }} label="Search TV Shows" />
</Tabs>
<div className="trending">
{content && content.map(c => <SingleContent key={c.id} id={c.id} poster={c.poster_path} title={c.title || c.name} date={c.first_air_date || c.release_date} media_type={type ? "tv" : "movie"} vote_average={c.vote_average} />)}
{noSearchResults && (type ? <h2>Tv Shows not found</h2> : <h2>Movies not found</h2>)}
</div>
{numOfPages > 1 && <CustomPagination setpage={setPage} numOfPages={numOfPages} />}
</div>
);
};
You can see this in action here.
The problem that happens is that even when I have something in my search results, it still shows the Movies(Tv Shows) not found message.
And then if you click the search button again it will disappear.
A similar thing happens when there are no search results.
Then the Movies(Tv Shows) not found message will not appear the first time, only when you press search again.
I don't understand what is going on. I have used .then after my async function and still it does not execute in that order.

Try adding noSearchResults to your useEffect hook. That hook is what tells React when to re-render, and right now it's essentially not listening to noSearchResult whenever it changes because it's not included in the array.

Related

Why do I need to click the Page number before I get the data first?

So I'm rendering filtered products, and now I want to create pagination. It is working but I need to click a page number first before it shows up. I already included a loading state, but it's not working properly.
My data is coming from the backend MongoDB
This is my first render:
After Clicking page 1
const ProductList = ({products, category}) => {
const [filteredProducts, setFilteredProducts] = useState([])
const [loading, setLoading] = useState(true)
useEffect(() =>{
const isAvailable = products.filter((product) => product.quantity !== 0 )
setFilteredProducts(isAvailable)
setLoading(false)
},[setFilteredProducts, category,products])
const firstIndex = 0
const [pageSize, setPageSize] = useState(5)
const [page, setPage] = useState(1)
const [data,setData] = useState(filteredProducts.slice(firstIndex, pageSize))
useEffect(() =>{
setData(filteredProducts.slice(0, pageSize))
setLoading(false)
},[pageSize])
const handleChange = (event, value) => {
setPage(value);
setData(filteredProducts.slice(firstIndex + pageSize * (value - 1), pageSize * value));
};
return (
<>
{loading ?
<BeatLoader
color="#36d7b7"
loading={loading}
size={50}
aria-label="Loading Spinner"
data-testid="loader"
/>
:
(
<Box sx={{backgroundColor: '#f5f5f5', display:'flex', marginTop:2}}>
<Container maxWidth="xl">
<Typography sx={{textAlign: 'center', paddingY: '20px', fontWeight: 700, color: '#212121', typography: {xs: "h6", md: "h4"}}}>Products</Typography>
<Box sx={{display: 'flex', alignItems:'center',justifyContent: 'space-evenly', flexWrap: 'wrap', gap: '10px'}}>
{data.map((product) => (
<ProductItem key={product._id} product={product} />
))}
</Box>
<Pagination
sx={{display: 'flex', alignItems:'center',justifyContent:'center',margin: 4}}
size="large"
count={Math.ceil(filteredProducts.length/pageSize)}
page={page}
onChange={handleChange}
/>
</Container>
</Box>
)
}
</>
)
}
export default ProductList
It looks like the second useEffect is using the value of filteredProducts but not having it in the dependencies array, so it could not update data when it is ready or if it changes.
This seems to be the reason data could only be updated with the handleChange event.
To fix this, try add filteredProducts to the dependencies array:
useEffect(() => {
setData(filteredProducts.slice(0, pageSize));
setLoading(false);
// 👇 Add this here
}, [filteredProducts, pageSize]);
There might be other issues that need to be addressed, but hope that this still helps.
As for loading, not too sure but it seems to start as true but is immateriality set to false as soon as useEffect runs. If this component is not toggling the loading value, it might not need to be a state.
Perhaps consider to render the spinner based on condition, for example if product is empty [] (usually it is when the data is being fetched).
Alternatively perhaps handle a loading state in the parent component where the data is fetched could also work, such as {loading ? <Spinner /> : <ProductList />}.

Show modal window without button press in react

I am doing a project and I want to show a modal window to compulsorily accept some Cookies.
I need that cookie window to be launched automatically, without pressing any button, when the application starts, that is, before the dashboard is loaded.
This is my code:
ModalCookie.js
imports ...
const style = {
position: 'absolute',
top: '50%',
left: '50%',
};
export default function CookiesModal() {
const handleOpen = () => setOpen(true);
const [open, setOpen] = React.useState(false);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setCookiesPreview(false);
setOpen(false);
};
const handleNotClose = (event, reason) => {
if (reason && reason == "backdropClick")
return;
}
const [value, setValue] = React.useState('1');
const handleChange = (event, newValue) => {
setValue(newValue);
};
return (
<ThemeProvider theme={theme}>
<div>
<Fade in={open}>
<Box>
<BootstrapDialog
onClose={handleNotClose}
aria-labelledby="customized-dialog-title"
open={open}
closeAfterTransition
BackdropComponent={Backdrop}
BackdropProps={{
timeout: 500,
}}
>
<BootstrapDialogTitle id="customized-dialog-title" onClose={handleClose}>
</BootstrapDialogTitle>
<TabContext value={value}>
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
<TabList onChange={handleChange} aria-label="lab API tabs example">
<Tab
label="AAAAA"
value="1" />
</TabList>
</Box>
<TabPanel value="1">
<p>...</p>
</TabPanel>
</TabContext>
<Button
onClick={handleClose}
variant="contained"
color="secondary"
>
Aceptar
</Button>
</BootstrapDialog>
</Box>
</Fade>
</div>
</ThemeProvider>
);
}
Config.js
// COOKIES
export const setCookiesPreview = ( state ) => {
localStorage.setItem('cookiesPreview', state)
}
// --
export const isCookiesPreview = () => {
const active=localStorage.getItem('cookiesPreview') ? localStorage.getItem('cookiesPreview') : true;
setCookiesPreview(false);
return(
active
);
}
And my dashboard:
imports...
// MAIN
export const Dashboard = () => {
const state = useSelector( state => state);
console.log('isCookiesPreview='+isCookiesPreview());
if(isCookiesPreview()){
console.log('CookiesPreview ON ------------------------------------------------')
setTimeout(() => {
CookiesModal.handleOpen();
}, 15000);
}
return (
<>
<ThemeProvider theme={theme}>
<div>
<HeaderBar/>
{(alertMessage != null) && (alertMessage.length>0) ? <Alert severity={alertSeverity}>{alertMessage}</Alert> : <></>}
<Grid container item={true} xs={12}>
<BodyGrid/>
</Grid>
<Grid container item={true} xs={12} pt={4}>
<BottomBar/>
</Grid>
</div>
</ThemeProvider>
</>
)
I am trying to use the handleOpen() constant from ModalCookie.js to open the window from Dashboard.js and save the locale that those cookies have been accepted so as not to show it the following times
I can't get the window to show up, but it does show me the logs I've put on the Dashboard related to cookies.
It tells me that HandleOpen is not a function.

Component rerendering only after double click

I have a parent component that is passing products down into a subcomponent as state along with the product's filters. For some reason I have to double click the "filters" in order for the parent component to rerender with the filtered products. I understand because it is running asynchronously it is not updating the state immediately, but how can I force the update and rerender to run as soon as I add a filter without using forceUpdate? Is this where redux would come in to play?
Parent component
const [products, setProducts] = React.useState(data.pageContext.data);
const handleCount = () => {
setCount(count + 24);
}
return (
<div style={{width: "100%"}}>
<Header/>
<div style={{display: "flex", flexDirection: "row", justifyContent: "center"}}>
<Sidebar
products={products}
setProducts={setProducts}
baseProducts={data.pageContext.data}
/>
<div style={{display: "flex", flexDirection: "column"}}>
<h1 style={{width: "50%"}}>Cast vinyl</h1>
<h3>Product Count: {products.length}</h3>
<ProductList>
{products.slice(0, count).map(product => {
return (
<a href={`/vinyl/${product.data.sku}`}><div>
{product.data.field_product_image.length > 0 ?
<ProductImage images={data.data.allFiles} sku={`${product.data.sku}`}/> :
<StaticImage src="http://stagingsupply.htm-mbs.com/sites/default/files/default_images/drupalcommerce.png" width={250} alt=""/>}
<h3>{product.data.title}</h3>
<h5>{product.data.sku}</h5>
</div></a>
)
})}
</ProductList>
<h3 onClick={handleCount}>Load more</h3>
</div>
</div>
</div>
)
Child Component
const Sidebar = ({ setProducts, baseProducts }) => {
const [filters, setFilters] = React.useState([]);
const [click, setClick] = React.useState(false);
const handleClick = () => {
setClick(!click);
}
const onChange = (e) => {
if (!filters.includes(e)) {
setFilters([...filters, e])
}
if (filters.length > 0) {
const filteredProducts = baseProducts.filter(product => filters.includes(product.data.field_product_roll_size));
setProducts(filteredProducts);
}
}
const clearFilters = () => {
setFilters([]);
setProducts(baseProducts);
setClick(false);
}
const rollSize = [...new Set(baseProducts.map(fields => fields.data.field_product_roll_size))]
return (
<SidebarContainer>
<h3>Mbs Sign Supply</h3>
<ul>Sub Categories</ul>
<li>Calendered Vinyl</li>
<li>Cast Vinyl</li>
<h3>Filters</h3>
{filters.length > 0 ? <button onClick={clearFilters}>Clear Filters</button> : null}
<li onClick={() => handleClick()}>Roll Size</li>
{/*map through roll size array*/}
{/*each size has an onclick function that filters the products array*/}
{click ? rollSize.sort().map(size => {
return (
<span style={{display: "flex", flexDirection: "row", alignItems: "center", height: "30px"}}>
<Checkbox onClick={() => {onChange(size)}} />
<p >{size}</p>
</span>
)
}) : null}
<li>Width</li>
demo can be found at http://gatsby.htm-mbs.com:8000/cast-vinyl, clicking "Roll Size" from the left and then double clicking a filter
Thanks in advance
All I needed was a little useEffect
React.useEffect(() => {
if (filters.length > 0) {
const filteredProducts = baseProducts.filter(product => filters.includes(product.data.field_product_roll_size));
setProducts(filteredProducts);
}
}, [filters]);

React Redux state conditional bug and useEffect dependency bugs out

I have a component that gets a detail of a single post via params.id the component works when I remove all the dependencies on the useEffect but doesnt input the value from the redux state to the component states, when I put the dependencies back it gives me an error of title is undefined, but the redux action returns successfully as I have the data, I think the component loads first before receiving the redux action payload?
const UpdatePost = ({ match, history }) => {
const postId = match.params.id;
const [title, setTitle] = useState('');
const [description, setDescription] = useState('');
const [image, setImage] = useState('');
const dispatch = useDispatch();
const postDetails = useSelector(state => state.postDetails);
const { loading, error, post } = postDetails;
const postUpdate = useSelector(state => state.postUpdate);
const {
loading: loadingUpdate,
error: errorUpdate,
success: successUpdate,
} = postUpdate;
useEffect(() => {
if (successUpdate) {
dispatch({ type: POST_UPDATE_RESET });
dispatch({ type: POST_DETAILS_RESET });
history.push('/edit/post');
} else {
console.log(post, postId);
if (!post.title || post._id !== postId) {
dispatch(listPostDetails(postId));
} else {
setTitle(post.title);
setDescription(post.description);
setImage(post.image);
}
}
}, [post, history, dispatch, postId, successUpdate]);
const submitHandler = e => {
e.preventDefault();
dispatch(
updatePost({
_id: postId,
title,
description,
image,
})
);
};
return (
<MotionBox
exit={{ opacity: 0 }}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 1 }}
bgColor={'#eaeaea'}
my="1rem"
border="2px"
borderColor="#eaeaea"
borderRadius="25px"
p={{ base: '2rem 2rem' }}
>
<Heading color={'#435943'} size="lg" pb={'1.5rem'}>
Edit User
</Heading>
{loadingUpdate && <Loader />}
{errorUpdate && <Message status="error">{errorUpdate}</Message>}
{loading && loadingUpdate ? (
<Loader />
) : error ? (
<Message status="error">{error}</Message>
) : (
<form onSubmit={submitHandler}>
<FormControl pb={'1rem'}>
<FormLabel size="md">Edit Post Title</FormLabel>
<Input
borderColor="#d4d6d5"
placeholder="Edit Post Title"
value={title}
onChange={e => setTitle(e.target.value)}
/>
</FormControl>
<FormControl pb={'1rem'}>
<FormLabel size="md">Edit Post Description</FormLabel>
<Textarea
size="md"
value={description}
onChange={e => setDescription(e.target.checked)}
placeholder="Edit Post Descrription"
/>
</FormControl>
<FormControl pb={'1rem'}>
<FormLabel size="md">Edit Post Photo</FormLabel>
<Input
size="md"
placeholder="Edit Post Photo"
value={image}
onChange={e => setImage(e.target.checked)}
/>
</FormControl>
<Button
type="submit"
my={'2rem'}
fontSize={'md'}
fontWeight={600}
color="white"
bg={'green.800'}
_hover={{
background: 'green.600',
}}
_focus={{
outline: 'none',
border: 'none',
}}
>
Update
</Button>
</form>
)}
</MotionBox>
);
};

How to get my Autocomplete input field to reset DefaultValue after submit?

How can I get my <TextField> inside Autocomplete to reset it's default value after form submit?
Currently, the state of formValues during submit remains as the default value?
I've tried to fix the onSubmit function of my form to clear the state of the values, but not able to do so.
How can I clear the value after a user submits?
const { control, handleSubmit } = useForm();
const [formValues, SetFormValues] = useState()
const onSubmit = (data, e) =>
{
console.log(data);
axiosInstance
.patch(URL + slug + '/', {
stock_list: data.stock_list.map(list=>list.symbol),
})
.then((res) =>
{
getFinData(dispatch)(slug);
SetFormValues([''])
console.log(formValues)
});
};
console.log(formValues)
return (
<Container component="main" maxWidth="md">
<div className={classes.container}>
<Grid>
<form noValidate onSubmit = { handleSubmit(onSubmit) }>
<Controller
render={({ onChange ,...props }) => (
<Autocomplete
{...props}
className={classes.inputBar}
id="stock_list"
key={formValues}
name="stock_list"
multiple
options={options}
ListboxComponent={ListboxComponent}
renderGroup={renderGroup}
filterOptions={filterOptions}
filterSelectedOptions
// onChange={(e) => onChange(e.target.value)}
onChange={(e, data) => { onChange(data); SetFormValues(data) }}
getOptionLabel={(option) => option.symbol}
getOptionSelected={(option, value) => option.symbol === value.symbol}
renderOption={(option) =>
{
return (
<>
<span style={{ fontWeight: 500, fontSize: "20px", paddingRight: "1rem" }}>{option.symbol}</span><span style={{ color: "#C6C6C6", fontSize: "24px" }}> | </span><span style={{ paddingLeft: "1rem" }}>{option.company}</span>
</>
)
}}
renderInput={(params) => (
<Zoom in={tabSwitch === 0}>
<TextField
{...params}
style={{ alignItems: 'center' }}
id="stock_list"
name="stock_list"
variant="outlined"
label="Companies"
className={classes.inputBar}
defaultValue={formValues}
/>
</Zoom>
)}
/>
)}
name="stock_list"
control={control}
defaultValue={formValues}
// onChange={([, data]) => data}
/>
{formValues && formValues.length > 0 &&
<Button
variant="contained"
color="primary"
type="submit"
style={{display:"flex",alignItems: 'center',justifyContent:"center"}}
>
Add Stocks
</Button>
}
</form>
</Grid>
</div>
</Container>
);
})
UPDATE:
I have tried implementing your codes but still no success in removing the data from original state?
const [formValues, SetFormValues] = useState([])
const onSubmit = (data, e) =>
{
console.log(data);
axiosInstance
.patch(URL + slug + '/', {
stock_list: data.stock_list.map(list=>list.symbol),
})
.then((res) =>
{
getFinData(dispatch)(slug);
SetFormValues([]);
});
};
return (
<Controller
render={({ onChange ,...props }) => (
<Autocomplete
{...props}
className={classes.inputBar}
id="stock_list"
key={formValues}
name="stock_list"
multiple
options={options}
ListboxComponent={ListboxComponent}
renderGroup={renderGroup}
filterOptions={filterOptions}
filterSelectedOptions
onChange={(e, data) => { onChange(data); SetFormValues(data) }}
getOptionLabel={(option) => option.symbol}
getOptionSelected={(option, value) => option.symbol === value.symbol}
renderOption={(option) =>
{
return (
<>
<span style={{ fontWeight: 500, fontSize: "20px", paddingRight: "1rem" }}>{option.symbol}</span><span style={{ color: "#C6C6C6", fontSize: "24px" }}> | </span><span style={{ paddingLeft: "1rem" }}>{option.company}</span>
</>
)
}}
renderInput={(params) => (
<Zoom in={tabSwitch === 0}>
<TextField
{...params}
style={{ alignItems: 'center' }}
id="stock_list"
name="stock_list"
variant="outlined"
label="Companies"
className={classes.inputBar}
defaultValue={formValues}
value={formValues}
/>
</Zoom>
)}
/>
)}
name="stock_list"
control={control}
defaultValue={[]}
/>
It might be worth noting that my Mui AutoComplete and textfield is wrapped around by React Hook Form controller.
There is error in your code.
When using state, specify the data type to be stored.
E.g
if you are storing array of data, your state should be const [formValues, SetFormValues] = useState([]); not const [formValues, SetFormValues] = useState();
if you are storing string of data, your state should be const [formValues, SetFormValues] = useState(""); not const [formValues, SetFormValues] = useState();
if you are storing integer of data, your state should be const [formValues, SetFormValues] = useState(0); not const [formValues, SetFormValues] = useState();
To clear the state of each of these data
For Array
SetFormValues([]);
For String
SetFormValues("");
For Int
SetFormValues(0);
So correct your code and ty again. It will work for you.
put this code in the then callback of axios SetFormValues([]);
Because defaultValue is default value..
Try looks like this; (add value)
<TextField
style={{ alignItems: 'center' }}
id="stock_list"
name="stock_list"
variant="outlined"
label="Companies"
defaultValue={formValues}
value={formValues}
/>
https://material-ui.com/api/input/#main-content
defaultValue: The default input element value. Use when the component is not controlled.
value: The value of the input element, required for a controlled component.
You can do it this way
const [formValues, SetFormValues] = useState(null)
Put this code in the then callback of axios SetFormValues(null);
OR
const [formValues, SetFormValues] = useState([])
Put this code in the then callback of axios SetFormValues(null);
If any of those two above does not work for you, try storing the values as string in state
E.g const [formValues, SetFormValues] = useState("")
and clear items using SetFormValues(null) or SetFormValues("");

Categories