Router push not working from within useEffect hook - javascript

I have the following component where I am expecting to go to another path via router's push method immediately cos router push is inside the useEffect` hook.
But router push never seems to happen. The ShowLoading component is just a loading screen (shows a loading spinner).
The page is just stuck on the loading spinner screen.
What am I doing wrong, why am I not being pushed to the new page? Pls advice. Thanks.
import React from 'react';
import Cookies from 'js-cookie';
import { ShowLoading } from '../../ShowLoading';
import { withRouter } from 'react-router';
const MyComponent = ({
router: { push, location },
}) => {
React.useEffect(() => {
// this cookie gets set thus clearly we are entering this useEffect.
Cookies.set('temp', '1');
const value = 'test';
const params = location.search ? `${location.search}` : '';
// seems like this has no effect, no pushing and moving to new page path.
push(`/${value}/my_path${params}`);
}, []);
return (<ShowLoading />);
};
export default (withRouter(MyComponent);
P.S: Goes to a new path as expected if I do the following but looking to do it via a router.
window.location.pathname = `/${value}/my_path${params}`;

You can get match, history and location as props when using withRouter. So you can get the push from history like this:
import React from 'react';
import Cookies from 'js-cookie';
import { ShowLoading } from '../../ShowLoading';
import { withRouter } from 'react-router';
const MyComponent = ({
history,
location
}) => {
React.useEffect(() => {
// this cookie gets set thus clearly we are entering this useEffect.
Cookies.set('temp', '1');
const value = 'test';
const params = location.search ? `${location.search}` : '';
history.push(`/${value}/my_path${params}`);
}, []);
return (<ShowLoading />);
};
export default withRouter(MyComponent);

Related

React app not running useEffect when tab is not in focus

I have a react app receiving updates to all subscribed clients via socket,
When a new socket message comes in, I’m firing a dispatch action
io.on(EVENT, ({ someData }) => {
console.log(newDate());
dispatch(handleEventUpdate(someData));
});
This action only gets fired when the tab gets in focus.
The date log also matching the exact time the tab comes in focus, any ideas how to make this execute even when the tab is not in focus?
Since JS doesn’t run when tab is not in focus which I think is the issue, I’m Currently trying to use a service worker approach to handle this but I’m not sure where to start from.
Context
import React, { useEffect } from 'react';
import { useSelector } from 'react-redux';
import ChildComp from './ChildComp';
import React, { useEffect } from 'react';
import { useSelector } from 'react-redux';
import ChildComp from './ChildComp';
const ParentComp = () => {
const { data } = useSelector(
(state) => state,
);
return <ChildComp data={data} />;
};
export default ParentComp;
import React, { useEffect } from 'react';
const ChildComp = ({ data }) => {
useEffect(() => {
console.log('state changed');
console.log(data);
return () => {
console.log('component cleanup');
};
}, [data]);
return <p> {data} </p>;
};
export default ChildComp;
The parent gets the state data and passes to the child component. The update doesn't reflect if the tab isn't in focus until you go back to the tab.
You can try the following code simply by adding a dependency. The issue here is that you are not providing enough code...
io.on(EVENT, ({ someData }) => {
console.log(newDate());
dispatch(handleEventUpdate(someData));
}, [/*a value of foucus and not foucus that changes ! that will rerender the component*/]);

Infinite loop (react firebase firestore reactfire)

I have the following react component that creates a new document ref and then subscribes to it with the useFirestoreDocData hook.
This hook for some reason triggers an infinite rerender loop in the component.
Can anyone see what might cause the issue?
import * as React from 'react';
import { useFirestore, useFirestoreDocData, useUser } from 'reactfire';
import 'firebase/firestore';
import 'firebase/auth';
import { useEffect, useState } from 'react';
import { Link, useHistory } from 'react-router-dom';
interface ICreateGameProps {
}
interface IGameLobbyDoc {
playerOne: string;
playerTwo: string | null;
}
const CreateGame: React.FunctionComponent<ICreateGameProps> = (props) => {
const user = useUser();
const gameLobbyDocRef = useFirestore()
.collection('GameLobbies')
.doc()
//This for some reason triggers an infinite loop
const { status, data } = useFirestoreDocData<IGameLobbyDoc>(gameLobbyDocRef);
const [newGameId, setNewGameId] = useState('')
const history = useHistory();
useEffect(() => {
async function createGameLobby() {
const gl: IGameLobbyDoc = {
playerOne: user.data.uid,
playerTwo:null
}
if (user.data.uid) {
const glRef = await gameLobbyDocRef.set(gl)
setNewGameId(gameLobbyDocRef.id)
}
}
createGameLobby()
return () => {
gameLobbyDocRef.delete();
}
}, [])
return <>
<h2>Gameid : {newGameId}</h2>
<p>Waiting for second player to join...</p>
<Link to="/">Go Back</Link>
</>
};
export default CreateGame;
The problem is when you're calling doc() without arguments:
firestore creates new document ref each time with new auto-generated id.
you pass this reference to the useFirestoreDocData that is responsible for creating and observing this document.
useFirestoreDocData makes request to the server to inform about new draft document.
server responds to this request with ok-ish response (no id conflicts, db is accessible, etc...).
created observer updates status of the created document
that triggers rerender (since the document data has updated).
on new rerender .doc() creates new document ref
gives it to the useFirestoreDocData and...
I believe you've got the idea.
To break out of this unfortunate loop we should ensure the .doc() call happens only once on the first render and the ref created by the it doesn't change on each rerender. That's exactly what useRef is for:
...
const gameLobbyDocRef = React.useRef(useFirestore()
.collection('GameLobbies')
.doc())
const { status, data } = useFirestoreDocData<IGameLobbyDoc>(gameLobbyDocRef.current);
...

Why does router.query return an empty object in NextJS on first render?

My url is: http://localhost:3000/company/60050bd166cb770942b1dadd
I want to get the value of the id by using router.query. However when I console log router.query, it returns an empty object first and then return the object with data. This results in bugs in other parts of my code as I need the value of the id to fetch other data.
This is my code:
import { useRouter } from 'next/router';
import styles from './CompanyId.module.css';
import { useQuery } from '#apollo/client';
import { COMPANY_DETAILS } from '../../queries/company';
const CompanyDetails = () => {
const router = useRouter();
console.log(router.query);
const { loading, data } = useQuery(COMPANY_DETAILS, {
variables: { _id: companyId },
});
return (
<div className={styles.container}>
{loading ? <h1>Loading</h1> : <h1>{data.company.name}</h1>}
</div>
);
};
export default CompanyDetails;
My program is crashing right now because the companyId variable is empty on the first render. Is there anyway to go around this problem?
In Next.js:
Pages that are statically optimized by Automatic Static Optimization will be hydrated without their route parameters provided, i.e query will be an empty object ({}).
After hydration, Next.js will trigger an update to your application to provide the route parameters in the query object.
I solved it by using useLazyQuery instead of useQuery, and wrapped the function inside useEffect.
The problem was that NextJS's router.query returns an empty object on the first render and the actual object containing the query comes in at the second render.
This code works:
import React, { useEffect } from 'react';
import { useRouter } from 'next/router';
import styles from './CompanyId.module.css';
import { useLazyQuery } from '#apollo/client';
import { COMPANY_DETAILS } from '../../queries/company';
const CompanyDetails = () => {
const router = useRouter();
const [getCompany, { loading, data }] = useLazyQuery(COMPANY_DETAILS);
useEffect(() => {
if (router.query.companyId) {
getCompany({ variables: { _id: router.query.companyId } });
}
}, [router.query]);
if (loading) return <h1>Loading....</h1>;
return (
<div className={styles.container}>
{data && <h1>{data.company.name}</h1>}
</div>
);
};
export default CompanyDetails;

Get query string value in React JS

I am having URL as http://example.com/callback?code=abcd
I need to fetch value of code.
Below is my code:
import React from 'react';
import { useEffect } from 'react';
const Callback = () => {
useEffect(() => {
console.log("hiii");
}, []);
return (
<React.Fragment>
Callback
</React.Fragment>
);
};
export default Callback;
Please confirm how can I fetch that.
Thanks in advance.
Probably the best way is to use the useParams hook because you have a functional component. Read from the documentation as the following:
useParams returns an object of key/value pairs of URL parameters. Use it to access match.params of the current <Route>.
I would suggest the following:
import React from 'react'
import { useParams } from 'react-router-dom'
const Callback = () => {
let { code } = useParams()
console.log({ code });
return (
<React.Fragment>
Callback
</React.Fragment>
)
};
+1 from documentation:
You need to be using React >= 16.8 in order to use any of these hooks!
use this library 'query-string'
and use as
import queryString from 'query-string';
in constructor
constructor(props) {
const { location: { search } } = props;
const parsed = queryString.parse(search);
this.state = {
code: parsed.code,
}
}
hope it will solve your problem !
Try this
http://example.com/callback?code=abcd
// ReactJS
import React from "react";
import { useLocation } from "react-router-dom";
const MyComponent = () => {
const search = useLocation().search;
const code = new URLSearchParams(search).get("code");
console.log(code); //abcd
}
// VanillaJS
const code = window.location.search.split("=")[1];
console.log(code); //abcd
You can use the URLSearchParams interface to fetch the query string of a URL.
https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams
const queryParams = new URLSearchParams(window.location.search);
const code = queryParams.get("code");
First at App.js you should have react-router-dom
import { Route, Switch } from "react-router-dom";
Then inside switch you can define route parameters like in your case
<Route exact path="/callback/:code" component={YourComponentsName} />
At the components file you can get the parameters from the props
Functional Component
const code = props.match.params.code
Class Component
const code = this.props.match.params.code
Or next approach
const code = new URLSearchParams(this.props.location.search);
const code = query.get('code')
console.log(code)//abcd
You need to use a hook...
useEffect(() => {
let urlParams = new URLSearchParams(window.location.search);
console.log(urlParams.code || "empty");
}, []);

Test conditionals and useEffect in React with Jest

I need to write a test with the following steps:
get user data on mount
get project details if it has selectedProject and clientId when they change
get pages details if it has selectedProject, clientId, and selectedPages when they change
render Content inside Switch
if doesn't have clientId, Content should return null
if doesn't have selectedProject, Content should return Projects
if doesn't have selectedPages, Content should return Pages
else Content should render Artboard
And the component looks like this:
import React, { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { getUserData } from "../../firebase/user";
import { selectProject } from "../../actions/projects";
import { getItem } from "../../tools/localStorage";
import { getProjectDetails } from "../../firebase/projects";
import { selectPages } from "../../actions/pages";
import Pages from "../Pages";
import Projects from "../Projects";
import Artboard from "../Artboard";
import Switch from "../Transitions/Switch";
import { getUserId, getClientId } from "../../selectors/user";
import { getSelectedProject } from "../../selectors/projects";
import { getSelectedPages, getPagesWithDetails } from "../../selectors/pages";
import { getPagesDetails } from "../../firebase/pages";
const cachedProject = JSON.parse(getItem("selectedProject"));
const cachedPages = JSON.parse(getItem("selectedPages"));
const Dashboard = () => {
const dispatch = useDispatch();
const userId = useSelector(getUserId);
const clientId = useSelector(getClientId);
const selectedProject = useSelector(getSelectedProject) || cachedProject;
const selectedPages = useSelector(getSelectedPages) || cachedPages;
const pagesWithDetails = useSelector(getPagesWithDetails);
useEffect(() => {
dispatch(
getUserData(userId)
);
cachedProject && selectProject(cachedProject);
cachedPages && selectPages(cachedPages);
}, []);
useEffect(() => {
if (selectedProject && clientId) {
dispatch(
getProjectDetails(
clientId,
selectedProject
)
);
}
}, [selectedProject, clientId]);
useEffect(() => {
if (selectedPages && selectedProject && clientId) {
const pagesWithoutDetails = selectedPages.filter(pageId => (
!Object.keys(pagesWithDetails).includes(pageId)
));
dispatch(
getPagesDetails(
selectedProject,
pagesWithoutDetails
)
);
}
}, [selectedPages, selectedProject, clientId]);
const Content = () => {
if (!clientId) return null;
if (!selectedProject) {
return <Projects key="projects" />;
}
if (!selectedPages) {
return <Pages key="pages" />;
}
return <Artboard key="artboard" />;
};
console.log("Update Dashboard")
return (
<Switch>
{Content()}
</Switch>
);
};
Where I use some functions to fetch data from firebase, some to dispatch actions, and some conditionals.
I'm trying to get deep into testing with Jest and Enzyme. When I was searching for testing approaches, testing useEffect, variables, and conditions, I haven't found anything. All I saw is testing if a text changes, if a button has get clicked, etc. but what about testing components which aren't really changing anything in the DOM, just loading data, and depending on that data, renders a component?
What's the question here? What have you tried? To me it seems pretty straightforward to test:
Use Enzymes mount or shallow to render the component and assign that to a variable and wrap it in a store provider so it has access to a redux store.
Use jest.mock to mock things you don't want to actually want to happen (like the dispatching of actions) or use something like redux-mock-store.
Use that component ".find" to get the actual button you want.
Assert that, given a specific redux state, it renders correctly.
Assert that actions are dispatched with the proper type and payload at the proper times.
You may need to call component.update() to force it to rerender within the enzyme test.
Let me know if you have more specific issues.
Good luck!

Categories