I have on one URL (pages/index.js) an <input onFocus={() => router.push('/search')} />, and on the /search url (pages/search.js) I have a full form. However, it jolts and flickers when going from one screen to the next. How do I avoid that?
You may want to setup nprogress loading when navigating between pages :
_app.js
import NProgress from "nprogress";
import Router from "next/router";
import "nprogress/nprogress.css";
NProgress.configure({ showSpinner: true });
Router.events.on("routeChangeStart", () => {
NProgress.start();
});
Router.events.on("routeChangeComplete", () => {
NProgress.done();
});
Custom css for nprogress :
#nprogress {
color: green !important;
}
#nprogress .bar {
background: green !important;
}
#nprogress .spinner-icon {
border-top-color: green !important;
border-left-color: green !important;
}
Note
If you don't want to have a flicker meaning no navigating between pages then you should handle input focus with states to show the form :
function Form() {
const [show , setShow] = React.useState(false)
return (
<div>
{show ? <YourFormComponent/> : <input onFocus={()=>setShow(true)} ></input>}
</div>
)
}
Extra Note
If you want to update the url and have your inputs show I suggest using queries :
import React from "react";
import { useRouter } from "next/router";
import queryString from "query-string";
function FormWithQuery() {
const [show, setShow] = React.useState(false);
const router = useRouter();
const parsed = queryString.parse(router.query);
React.useEffect(() => {
// your logic here
setShow(true);
}, [parsed]);
return (
<div>
{show ? (
<FormComponent />
) : (
<input onFocus={() => router.push("/?searchForm=true")} />
)}
</div>
);
}
export default FormWithQuery;
Related
I am building an image search app using Unsplash API and trying to implement react infinite scroll (https://www.npmjs.com/package/react-infinite-scroll-component), but it is not working properly.
Now, the search form works fine and it displays 10 images(which is the default number of images using Unsplash API) when you search something, but when I scroll down to the end of of the page, it only displays the loader (h4 'Loading') but it does not display more images.
App.js
import './App.css';
import Main from './components/Main';
function App() {
return (
<div className="App">
<Main />
</div>
);
}
export default App;
Main.js
import React from 'react'
import Header from './Header'
import Image from './Image'
import { useState, useEffect } from 'react'
import InfiniteScroll from 'react-infinite-scroll-component';
function Main() {
const [input, setInput] = useState('')
const [allImages, setAllImages] = useState([])
const [favorites, setFavorites] = useState(() => JSON.parse(localStorage.getItem("favorites")) || [])
useEffect(() => {
localStorage.setItem("favorites", JSON.stringify(favorites))
console.log(favorites)
}, [favorites])
function handleChange(event) {
setInput(event.target.value)
}
async function fetchImages() {
try {
const res = await fetch(`https://api.unsplash.com/search/photos?&query=${input}&client_id=${process.env.REACT_APP_UNSPLASH_API_KEY}`)
const data = await res.json();
setAllImages(data.results)
} catch(error) {
alert("Sum ting wong");
}
}
const handleSubmit = async (event) => {
event.preventDefault();
fetchImages()
}
console.log(`allImages: ${allImages.length}`);
// use parameter 'id' to read specific one
function isLiked(id) {
return favorites.find(el => el.id === id) ? true : false
}
return (
<main>
<Header
input={input}
handleChange={handleChange}
handleSubmit={handleSubmit}
/>
<InfiniteScroll
dataLength={allImages.length} //This is important field to render the next data
next={fetchImages}
hasMore={true}
loader={<h4>Loading...</h4>}
>
<div className='main--image-list mt-5 pb-5'>
{allImages.map(el => (
<Image
key={el.id}
// do need spread operator below for img's src to work in Image.js
{...el}
el={el}
isLiked={isLiked(el.id)}
favorites={favorites}
setFavorites={setFavorites}
/>
))}
</div>
</InfiniteScroll>
</main>
)
}
export default Main
for visuals
Is there a way we can have a loading state similar to when fetching data on the client-side?
The reason I would like a loading state is to have something like a loading-skeleton with for instance react-loading-skeleton
On the client-side we could do:
import useSWR from 'swr'
const fetcher = (url) => fetch(url).then((res) => res.json())
function Profile() {
const { data, error } = useSWR('/api/user', fetcher)
if (error) return <div>failed to load</div>
if (!data) return <div>loading...</div>
return <div>hello {data.name}!</div>
}
But for SSR (getServerSideProps) I cannot figure out if that is doable for example could we have a loading state?
function AllPostsPage(props) {
const router = useRouter();
const { posts } = props;
function findPostsHandler(year, month) {
const fullPath = `/posts/${year}/${month}`;
router.push(fullPath);
}
if (!data) return <div>loading...</div>; // Would not work with SSR
return (
<Fragment>
<PostsSearch onSearch={findPostsHandler} />
<PosttList items={posts} />
</Fragment>
);
}
export async function getServerSideProps() {
const posts = await getAllPosts();
return {
props: {
posts: posts,
},
};
}
export default AllPostsPage;
Recently Next.js has released getServerSideProps should support props value as Promise https://github.com/vercel/next.js/pull/28607
With that we can make a promise but am not sure how to implement that and have a loading state or if that is even achievable. Their example shows:
export async function getServerSideProps() {
return {
props: (async function () {
return {
text: 'promise value',
}
})(),
}
}
Currently watching Next.conf (25/10/2022) this issue looks promising:
https://beta.nextjs.org/docs/data-fetching/streaming-and-suspense
You can modify the _app.js component to show a Loading component while the getServerSideProps is doing async work like a fetch as shown here https://stackoverflow.com/a/60756105/13824894. This will apply on every page transition within your app.
You can still use your loading logic client-side independently.
you can set loading state on _app.js
import Router from "next/router";
export default function App({ Component, pageProps }) {
const [loading, setLoading] = React.useState(false);
React.useEffect(() => {
const start = () => {
console.log("start");
setLoading(true);
};
const end = () => {
console.log("findished");
setLoading(false);
};
Router.events.on("routeChangeStart", start);
Router.events.on("routeChangeComplete", end);
Router.events.on("routeChangeError", end);
return () => {
Router.events.off("routeChangeStart", start);
Router.events.off("routeChangeComplete", end);
Router.events.off("routeChangeError", end);
};
}, []);
return (
<>
{loading ? (
<h1>Loading...</h1>
) : (
<Component {...pageProps} />
)}
</>
);
}
My choice is to use isReady method of useRouter object
import { useRouter } from 'next/router'
import { useEffect, useState } from 'react'
function MyApp({ Component, pageProps }) {
const [isLoading, setIsLoading] = useState(true)
const router = useRouter()
useEffect(() => {
router.isReady && setIsLoading(false)
}, []
)
return <>{isLoading ? <>loading...</> : <Component {...pageProps} />}</>
}
export default MyApp
I have not tried this feature yet but in theory I think it should work. If all you want is to have the client side access to a promise via server props, try as below. Basically your props is a async lambda function so you do any work needed e.g fetching data etc inside it so the client-side should access props as a promise and await for it.
export async function getServerSideProps() {
return {
props: (async function () {
const posts = await getAllPosts();
return {
posts: posts,
}
})(),
}
}
//then on client-side you can do the following or similar to set loading state
function MyComponent(props) {
const [isLoading, setIsLoading] = useState(false);
const [posts, setPosts] = useState({});
useEffect(async () => {
setIsLoading(true);
const tempPosts = await props?.posts;
setPosts(posts);
setIsLoading(false);
}, [])
return (
{isLoading && <div>loading...</div>}
);
}
export default MyComponent;
This works for me using MUI v.5
import Router from "next/router";
import Head from "next/head";
import { useEffect, useState } from "react";
import { CacheProvider } from "#emotion/react";
import {
ThemeProvider,
CssBaseline,
LinearProgress,
CircularProgress,
circularProgressClasses,
Box,
} from "#mui/material";
import { alpha } from "#mui/material/styles";
import createEmotionCache from "/src/createEmotionCache";
import theme from "/src/theme";
import Layout from "/src/components/layout/Layout";
// Client-side cache, shared for the whole session of the user in the browser.
const clientSideEmotionCache = createEmotionCache();
function Loader(props) {
return (
<Box
sx={{
position: "fixed",
top: 0,
left: 0,
right: 0,
}}
>
<LinearProgress />
<Box sx={{ position: "relative", top: 8, left: 8 }}>
<CircularProgress
variant="determinate"
sx={{
color: alpha(theme.palette.primary.main, 0.25),
}}
size={40}
thickness={4}
{...props}
value={100}
/>
<CircularProgress
variant="indeterminate"
disableShrink
sx={{
animationDuration: "550ms",
position: "absolute",
left: 0,
[`& .${circularProgressClasses.circle}`]: {
strokeLinecap: "round",
},
}}
size={40}
thickness={4}
{...props}
/>
</Box>
</Box>
);
}
function MyApp({
Component,
pageProps,
emotionCache = clientSideEmotionCache,
}) {
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
Router.events.on("routeChangeStart", () => {
setIsLoading(true);
});
Router.events.on("routeChangeComplete", () => {
setIsLoading(false);
});
Router.events.on("routeChangeError", () => {
setIsLoading(false);
});
}, [Router]);
return (
<CacheProvider value={emotionCache}>
<Head>
<meta name="viewport" content="initial-scale=1, width=device-width" />
</Head>
<ThemeProvider theme={theme}>
<CssBaseline />
{isLoading && <Loader />}
<Layout>
<Component {...pageProps} />
</Layout>
</ThemeProvider>
</CacheProvider>
);
}
export default MyApp;
I used redux-saga and I want when I click on my button, the api will be fetching,
My code is:
// #flow
import React, { useEffect, useState, useRef } from 'react';
import { connect } from 'react-redux';
import { Row, Col, Card, CardBody, Button, ButtonDropdown, Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap';
import { Translate } from 'src/components';
import { VCS } from 'src/common';
import { ACCESS_LEVELS, USER_RIGHTS, userAccess } from 'src/constants/user-rights';
import * as Actions from './actions';
import ClientUsersRSuiteTable from './components/client-users-rsuite-table';
import './users.scss';
function Users({ clientId, clientUsers, requestClientUsersData, getUserTemplate, pageParameters, ...props }) {
const [searchValue, setSearchValue] = useState('');
useEffect(() => {
requestClientUsersData({ id: clientId, pageParams: null });
}, []);
const handleChangeSearchValue = (input) => {
const search = input != '' ? input : null;
setSearchValue(search);
};
const [dropdownOpen, setDropdownOpen] = useState(false);
const toggle = () => setDropdownOpen(prevState => !prevState);
return (
<>
<VCS hasRights={[userAccess(ACCESS_LEVELS.EDIT, USER_RIGHTS.API_CLIENTS)]}>
<div className="row">
<div className="col">
<Button
style={{ backgroundColor: '#ffffff !important', color: '#fa5c7c !important' }}
outline
color="danger"
className="mb-2 mr-1 btn-user-template"
onClick={() => getUserTemplate(clientId)}
>
<i className="mdi mdi-file-outline mr-1" size="large" />
<Translate id="pages.client.users.get.user.template" />
</Button>
</div>
</div>
</div>
</VCS>
</>
);
}
Users.defaultProps = {
};
const mapStateToProps = (state) => ({
clientUsers: state.Administration.users.clientUsers ? state.Administration.users.clientUsers :
state.Administration.clients.clientUsers,
pageParameters: state.Administration.users.clientUsersPageParameters ? state.Administration.users.clientUsersPageParameters :
state.Administration.clients.clientUsersPageParameters
});
export default connect(mapStateToProps, Actions)(Users);
My api is:
export const getUserTemplate = async (clientId) => request(`api/clients/${clientId}/users/import/template`, 'GET');
When I click on the button, my api is called two times with same response !
The Api is to export excel data, when I run it, I get :
I want when I run it on clicking the button, I get just one file not two(because it runs two time)
How can I fix it ?
I'm trying to display text when I hover over an icon, but if you hover the icons quickly it'll get stuck on displaying the text instead of displaying the icon (default state)
ex: https://giphy.com/gifs/UsS4JcRJGV5qfCI5VI
Skills Component:
import React, { useState } from 'react';
import { UserIcon } from './AboutBtnStyling';
import IconText from '../../../IconText';
const AboutBtn = () => {
const [hover, setHover] = useState(false);
const onHover = () => {
setHover(true)
}
const onLeave = () => {
setHover(false)
}
return (
<div onMouseEnter={onHover} onMouseLeave={onLeave} role="button">
{hover ? <IconText text="ABOUT" /> : <UserIcon /> }
</div>
)
}
export default AboutBtn;
Then I hoped converting it to a class component would help, bc of stale closure problem associate with useState hook
import React, { Component } from 'react';
import { SkillIcon } from './SkillsBtnStyling';
import IconText from '../../../IconText';
class SkillsBtn extends Component {
constructor(props) {
super(props);
this. state = { hover: false }
}
onHover = () => {
this.setState({ hover: true })
}
onLeave = () => {
this.setState({ hover: false })
}
render() {
return (
<div onMouseEnter={this.onHover} onMouseLeave={this.onLeave} role="button">
{this.state.hover ? <IconText text="SKILLS" /> : <SkillIcon /> }
</div>
)
}
}
export default SkillsBtn;
Would greatly appreciate any insight! I really want to solve this problem, instead of resorting to achieving this effect using CSS
An important aspect of useState is that it is asynchronous. I believe this is causing your code to act a bit buggy. I would add more decisiveness to your setState calls and set it (true/false) based on mouse position rather than toggle.
import React, { useState } from 'react';
import { SkillsButton } from './SkillsBtnElements'
const SkillsBtn = () => {
const [hover, setHover] = useState(false);
const onHover = () => {
setHover(!hover)
}
return (
<div onMouseEnter={() => setHover(true)} onMouseLeave={() =>
setHover(false)} role="button" tabIndex='-3' >
{ hover ? "SKILLS" : <SkillsButton /> }
</div>
)
}
export default SkillsBtn;
I can't get react-spring to work. I'm fairly new to this so I have no idea what is going wrong. I'm trying to make navbar appear from top to bottom to 40vh, but it doesn't appear to be recognizing the props passed. I used create-react-app and react-spring 8.0.27
App.js:
const App = () => {
const [open, setOpen] = useState(false);
const navprops = useSpring({
from: {height: "0"},
to: {height: "40vh"}
})
return (
<Fragment>
{open ? <Navbar style={navprops}/> : null}
</Fragment>
Navbar.js:
const NavBar = styled(animated.nav)`
width: 100%;
`;
const Navbar = (props) => {
return (
<NavBar style={props.style}>
</NavBar>
);
};
This is basically the code. There are more style props but I guess it's irrelevant to functionality.
animated and useSpring are imported in both files for testing. Thank you for your help.
Here is my solution,
Demo Link
Navbar.js
import React from "react";
import styled from "styled-components";
import { animated } from "react-spring";
const NavBar = styled(animated.nav)`
width: 100%;
background: red;
`;
export default (props) => {
return <NavBar style={props.style}></NavBar>;
};
App.js
import React, { useState } from "react";
import { useTransition, config } from "react-spring";
import Navbar from "./Navbar";
export default function App() {
const [open, setOpen] = useState(false);
// const navprops = useSpring({
// from: { height: "0" },
// to: { height: "40vh" },
// config: config.wobbly
// });
const transitions = useTransition(open, null, {
initial: { height: "0px" }, //Not required
from: { height: "0px" },
enter: { height: "40vh" },
leave: { height: "0px" },
config: config.wobbly //More configs here https://www.react-spring.io/docs/hooks/api
});
return (
<div className="App">
{transitions.map(
({ item, key, props }) => item && <Navbar key={key} style={props} />
)}
<br />
<br />
<button onClick={() => setOpen(!open)}>Toggle Navbar</button>
</div>
);
}
I do not think useSpring will work on unmounted component. You were trying to animate an unmounted component.
According to documentation, useTransition can be used to animate mounting of unmounted components.
The syntax is little complicated, but they have made the syntax simpler in version 9(release candidate) of react-spring Link Here