How to add Spinner at specific route? - next.js

I get this code in question:nextjs getServerSideProps show loading
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} />
)}
</>
);
}
It work for me, but with all route, I just want to work with some route, and route have dynamic query, How can I do it?

First of all, you need to define a list of routes that you don't want to have a loading state. For example
//`/details/` can be a dynamic route like `/details/1`, `/details/2`, etc.
const EXCEPTED_ROUTES = ['/details/'] //routes based on definitions
And then you can do it with URL param in routeChangeStart event
const start = (url) => {
const isURLMatched = EXCEPTED_ROUTES.some(exceptedRoute => url.startsWith(exceptedRoute))
if(isURLMatched) { //the condition here is your choice
return //should not set loading state
}
console.log("start");
setLoading(true);
};

Related

React Query with firebase returns undefined

I am very new to state management libraries and the one that I chose was React Query. I have followed multiple tutorials about it with mock data, but when I try to use my firebase imported data, it returns undefined. I am attaching all of my code with instances of React Query in it. This app is in next js 12.
file where I want to fetch and render data
const fetchDbData = async () => {
const {currentUser} = UseAuth();
function getLoc() {
if (currentUser) {
return 'users/' + currentUser.uid + '/userInvestments'
} else {
return 'users/TestDocumentForDatabase/userInvestments'
};
}
const loc = getLoc();
const q = query(collection(db, loc));
const snapshot = await getDocs(q);
console.log(snapshot)
const {data, status, error} = useQuery(
['firebaseData'],
() => snapshot.forEach(doc => doc.data()),
{
refetchOnWindowFocus: false
},
{
retry: false
},
)
return {data, status, error}
}
export default function usePortfolio() {
const {data, status, error} = fetchDbData
.....
_app.js
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
useInfiniteQury: false,
},
}
})
export default function App({ Component, pageProps }) {
return (
<QueryClientProvider client={queryClient}>
<AuthProvider >
<RootLayout>
<Component {...pageProps} />
</RootLayout>
</AuthProvider>
</QueryClientProvider>
)}

React-query with NextJs routing

I have a page with few queries and it's all working until I update the URL params and then react-query stops working and also disappear from dev-tools.
When I click on a row it triggers the handleRowClick function which update the URL with params, and then react query stops working.
1.First Image
2.Clicking on a row ( Update URL params ) Second Image
const Volumes: NextPage = () => {
const apiRef = useGridApiRef()
const [volumes, setVolumes] = useState<IDataset[] | undefined>(undefined)
const [isOpen, setOpen] = useState(false)
const [rightWingData, setRightWingData] = useState<IDataset | undefined>(undefined)
const [searchValue, setSearch] = useState('')
const [volumeId, setVolumeId] = useState('')
const { isLoading, data } = useVolumeData()
const { isLoading: searchIsLoading, data: searchData } = useSearchVolumes(searchValue)
const { isLoading: volumeByIdLoading, data: volumeByIdData } = useVolumeDataById(volumeId)
const router = useRouter()
useEffect(() => {
if(router.isReady && router.query?.id && !rightWingData){
const volumeId = router.query.id.toString()
setVolumeId(volumeId)
}
if (!isLoading && data && !searchData) {
setVolumes(data.data.result)
}
else if (!searchIsLoading) {
setVolumes(searchData)
}
if(!volumeByIdLoading && volumeByIdData){
showVolumeData(volumeByIdData)
}
}, [data, isLoading, searchIsLoading, searchData, isOpen, rightWingData, volumeByIdLoading, volumeByIdData])
const handleRowClick = (rowData: IRowData) => {
showVolumeData(rowData.row)
Router.push({
pathname: '/volumes',
query: { id: rowData.row.id},
})
}
const showVolumeData = (volumeData: IDataset) => {
apiRef.current.updateColumns(thinColumns)
setRightWingData(volumeData)
setOpen(true)
setVolumeId('')
}
const closeRightWing = () => {
setOpen(false)
Router.push('/volumes')
apiRef.current.updateColumns(columns)
}
if (isLoading || !volumes) return <h1>Is Loading...</h1>
return (
<div className={`volumes ${isOpen ? "open" : "close"}`}>
<div className="volumes-table">
<InfTable setSearch={setSearch} searchValue={searchValue} apiRef={apiRef}
rows={volumes} columns={columns} initialState={initialState} onRowClick={handleRowClick} />
</div>
{rightWingData &&
<div className="right-wing-wrapper" >
<VolumeRightWing volume={rightWingData} onClose={closeRightWing} />
</div>
}
</div>
)
}
function MyApp({ Component, pageProps }: AppProps) {
const queryClient = new QueryClient()
return (
<QueryClientProvider client={queryClient}>
<Layout>
<Component {...pageProps} />
<ReactQueryDevtools initialIsOpen={false} position="bottom-right" />
</Layout>
</QueryClientProvider>
)
}
as shown in the code, you are re-creating a new QueryClient inside the App component:
function MyApp({ Component, pageProps }: AppProps) {
const queryClient = new QueryClient()
which means that every time the App re-renders, you throw away the Query Cache (which is stored inside the client).
This is likely what happens when you change the route params. If you cannot create the client outside of MyApp (e.g. because you are using server-side rendering), it is advised to create a stable client:
function MyApp({ Component, pageProps }: AppProps) {
const [queryClient] = React.useState(() => new QueryClient())
this is also shown in the SSR docs.

TypeError: dispatch is not a function when clicking the toggle button

I am using react redux-thunk. I have a set of users data that I get from an API and this is the schema:
.
I've connected the "active" property with the checked attribute of a Switch MUI button, so naturally when calling the API I have some users with their switch button already on "true". What I am trying to do is to just make the switch functional, and just be able to click it and change its state, not necessarily doing anything with that.
Here's my toggleType.js:
export const TOGGLE = "TOGGLE";
Here's my toggleAction.js:
import { TOGGLE } from "./toggleType";
const statusToggleAction = () => {
return {
type: TOGGLE,
};
};
export const statusToggle = () => {
return (dispatch) => {
dispatch(statusToggleAction);
};
};
Here's my toggleReducer.js:
import { TOGGLE } from "./toggleType";
const initialState = {
status: false,
};
const toggleReducer = (state = initialState, action) => {
switch (action.type) {
case TOGGLE:
status: true;
default:
return state;
}
};
export default toggleReducer;
Everything is under my userContainer.js, like that:
function UserContainer({ userData, fetchUsers }) {
useEffect(() => {
fetchUsers();
}, []);
return userData.loading ? (
<h2>Loading</h2>
) : userData.error ? (
<h2>{userData.error}</h2>
) : (
<Container maxWidth="lg" style={{ flexGrow: 1, height: "100%" }}>
<h2>User List</h2>
<div>
{userData &&
userData.users &&
userData.users.map((user) => (
<div key={user.id}>
<p>{user.name}</p>
<Switch checked={user.active} onChange={statusToggle()} />
</div>
))}
</div>
</Container>
);
}
const mapStateToProps = (state) => {
return { userData: state.user, statusToggle: state.status };
};
const mapDispatchToProps = (dispatch) => {
return {
fetchUsers: () => dispatch(fetchUsers()),
statusToggle: () => dispatch(statusToggle()),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(UserContainer);
This is the error I am getting whenever I am clicking one of those switches:
Any ideas are welcome, I "learned" redux like 3 days ago!
toggleReducer function in toggleReducer.js, replace status: true; with return { status: true }.
Just return action in statusToggle function in toggleAction.js without dispatch as following.
export const statusToggle = () => {
return statusToggleAction();
};
Or just call statusToggleAction directly in userContainer.js as following.
export const statusToggle = () => {
return (dispatch) => {
dispatch(statusToggleAction());
};
};

How to handle action dispatching for a nested React Redux component

I'm doing something like this for my UI component in a React Redux app:
// Outer.js
import Inner from './path'
export const Outer = () => {
return (
<div>
...
<Inner />
...
</div>
)
}
// Inner.js
const param = ''
export const Inner = () => {
return (
<div>
<TextField
input={param}
onChange = {(param) => {
Function(param)
}}
/>
</div>
)
}
I also set up a Container component for Outer.js:
// OuterContainer.js
import Outer from './path'
const mapStateToProps = (state) => {
paramToBeUpdated: ???
}
const mapDispatchToProps = (dispatch) => {
Function: (param) => dispatch(Function(param))
}
export default connect(mapStateToProps, mapDispatchToProps)(Outer)
My action created for this step:
action/index.js
export const Function = (param) => (dispatch, getState) => {
dispatch({ type: 'FUNCTION', param })
}
And my reducer included the following function:
// reducer.js
export default reducer = (state="", action) => {
switch(action.type) {
case 'FUNCTION':
return {
...state,
param: action.param
}
...
}
}
I'm trying to update the variable paramToBeUpdated's value from the Inner UI component. But it didn't work.
Can Inner and Outer components share a container component connected with Outer?
How should I do it without making too much changes to my current setup? Is it possible to avoid creating a new Inner container, which will basically be a copy of the Outer container?
If you can't connect Inner with the state value and or the action then you must have done something wrong, here is a working example:
const { Provider, connect } = ReactRedux;
const { createStore, applyMiddleware, compose } = Redux;
const { produce } = immer;
const initialState = {
value: '',
};
//action types
const CHANGE = 'CHANGE';
//action creators
const change = (value) => ({
type: CHANGE,
payload: value,
});
const reducer = (state, { type, payload }) => {
if (type === CHANGE) {
return produce(state, (draft) => {
draft.value = payload;
});
}
return state;
};
//creating store with redux dev tools
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
reducer,
initialState,
composeEnhancers(
applyMiddleware(() => (next) => (action) =>
next(action)
)
)
);
//components
const Inner = connect((state) => state.value, {
Function: change,
})(({ Function, value }) => {
return (
<input
type="text"
value={value}
onChange={(e) => Function(e.target.value)}
/>
);
});
const Outer = () => {
return (
<div>
<Inner />
</div>
);
};
ReactDOM.render(
<Provider store={store}>
<Outer />
</Provider>,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script>
<script src="https://unpkg.com/immer#7.0.5/dist/immer.umd.production.min.js"></script>
<div id="root"></div>
Only Outer is connected to the redux store.
If you want to dispatch an action from Inner you may do:
Connect Inner to the redux store
// Inner.js
const Inner = (props) => {
return (
<div>
<TextField
input={param}
onChange = {(param) => {
props.Function(param)
}}
/>
</div>
)
}
export default connect(null, mapDispatchToProps)(Inner)
no need to create any InnerContainer
Pass dispatch function from Outer (+ no need for Container)
// Outer.js
import Inner from './path'
export const Outer = (props) => {
return (
<div>
...
<Inner Function={props.Function} />
...
</div>
)
}
const mapStateToProps = (state) => {
paramToBeUpdated: ???
}
const mapStateToProps = (dispatch) => {
Function: (param) => dispatch(Function(param))
}
export default connect(mapStateToProps, mapDispatchToProps)(Outer)
// Inner.js
const Inner = (props) => {
return (
<div>
<TextField
input={param}
onChange = {(param) => {
props.Function(param)
}}
/>
</div>
)
}

Pass React.Context to Nextjs after ComponentDidMount?

I have an issue where I have a simple React.Context that's populated after all the components mount. The problem is that because it happens after mount, nextjs does not see this data on initial render, and so there's noticeable flicker.
Here's the simple component that sets the Context:
export const SetTableOfContents = (props: { item: TableOfContentsItem }) => {
const toc = useContext(TableOfContentsContext);
useEffect(() => {
// Updates the React.Context after the component mount
// (since useEffects run after mount)
toc.setItem(props.item);
}, [props.item, toc]);
return null;
};
Here's the React.Context. It uses React state to store the TOC items.
export const TableOfContentsProvider = (props: {
children?: React.ReactNode;
}) => {
const [items, setItems] = useState<TableOfContents["items"]>([]);
const value = useMemo(() => {
return {
items,
setItem(item: TableOfContentsItem) {
setItems((items) => items.concat(item));
},
};
}, [items]);
return (
<TableOfContentsContext.Provider value={value}>
{props.children}
</TableOfContentsContext.Provider>
);
};
Currently, it is not possible to set the React.Context before mount because React gives a warning---Cannot update state while render.
The only workaround I can think of is to use something other than React.state for the React.Context state---that way the component can update it any time it wants. But then the problem with that approach is that Context Consumers would no longer know that the items changed (because updates live outside the React lifecycle)!
So how to get the initial React.Context into the initial SSR render?
const items = [];
export const TableOfContentsProvider = (props: {
children?: React.ReactNode;
}) => {
const value = useMemo(() => {
return {
items,
setItem(item: TableOfContentsItem) {
items[item.index] = item;
},
};
// this dep never changes.
// when you call this function, values never change
}, [items]);
return (
<TableOfContentsContext.Provider value={value}>
{props.children}
</TableOfContentsContext.Provider>
);
};
Here's what I ended up doing:
render the app in getStaticProps using renderToString
use useRef for state in the Context instead of useState
the reason for doing this is because renderToString renders only the initial state. So if you update the Context using useState, it won't capture subsequent renders
update the Context on component initialization for the reason mentioned above
pass the Context an "escape hatch"---a function we can call to get the state calculated on the initial render
Yes, the whole thing seems like a giant hack! :-) I'm not sure if React.Context plays well with SSR :(
export const TableOfContentsProvider = (props: {
initialItems?: TableOfContentsItem[];
setItemsForSSR?: (items: TableOfContentsItem[]) => void;
children?: React.ReactNode;
}) => {
// use useRef for the reasons mentioned above
const items = useRef(props.initialItems || []);
// Client still needs to see updates, so that's what this is for
const [count, setCount] = useState(0);
const { setItemsForSSR } = props;
const setterValue = useMemo(
() => ({
setItem(item: TableOfContentsItem) {
if (!items.current.find((x) => x.id === item.id)) {
items.current.push(item);
items.current.sort((a, b) => a.index - b.index);
setCount((count) => count + 1);
setItemsForSSR?.(items.current);
}
},
}),
[setItemsForSSR]
);
const stateValue = useMemo(() => ({ items: items.current, count }), [count]);
return (
<TableOfContentsSetterContext.Provider value={setterValue}>
<TableOfContentsStateContext.Provider value={stateValue}>
{props.children}
</TableOfContentsStateContext.Provider>
</TableOfContentsSetterContext.Provider>
);
};
interface TableOfContentsSetterWorkerProps {
item: TableOfContentsItem;
setItem: (item: TableOfContentsItem) => void;
}
export class TableOfContentsSetterWorker extends React.Component<
TableOfContentsSetterWorkerProps,
{}
> {
constructor(props: TableOfContentsSetterWorkerProps) {
super(props);
// Need to do this on init otherwise renderToString won't record it
props.setItem(props.item);
}
render() {
return null;
}
}
/**
* Usage: use this as a child component when the parent needs to set the TOC.
*
* Exists so that a component can set the TOC without triggering
* an unnecessary render on itself.
*/
export function TableOfContentsSetter(props: { item: TableOfContentsItem }) {
const { setItem } = useContext(TableOfContentsSetterContext);
return <TableOfContentsSetterWorker item={props.item} setItem={setItem} />;
export const getStaticProps = async () => {
let initialTableOfContents: TableOfContentsItem[] = [];
const getItems = (items: TableOfContentsItem[]) => {
initialTableOfContents = [...items];
};
const app = () => (
<TableOfContentsProvider setItemsForSSR={getItems}>
<AppArticles />
</TableOfContentsProvider>
);
renderToString(app());
return {
props: {
initialTableOfContents,
},
};
};

Resources