How to change the router history in a functional component using react-redux - react-redux

I'm trying to migrate a code base from a class component to a functional component, but when I do that my history (browserHistory) and the redirects stop working.
I don't know if it's in the way I'm using useEffect, or forcing the history.push ? But I get a Warning: You cannot change <Router history> on Router and Connected Router. I get the right values from the state and redux and I can see the changes on the location when I console.log it is just that history.push that doesn't happen.
I'm using react-redux and connected-react-router.
What might the problem be?
Use case: Facebook API loads, when user signs in with Facebook, the user is redirected to another page.
This code works: class Component (old code)
class App extends Component {
constructor(props) {
super(props);
this.facebookInterval = undefined;
this.history = createBrowserHistory();
this.state = {
hasFB: false,
error: false,
};
}
/**
* Load the Facebook SDK
*
* #return {void}
*/
componentDidMount() {
this.facebookInterval = setInterval(() => {
if (window.FB !== undefined) {
this.setState({
hasFB: true,
});
clearInterval(this.facebookInterval);
}
}, 100);
window.fbAsyncInit = function () {
window.FB.init({
appId: process.env.REACT_APP_NEW_MESSENGER_APP_ID,
autoLogAppEvents: true,
xfbml: true,
version: 'v3.3',
});
};
(function (d, s, id) {
var js,
fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) {
return;
}
js = d.createElement(s);
js.id = id;
js.src =
'https://connect.facebook.net/en_US/sdk.js?xfbml=1&version=v9.0&appId=' +
process.env.REACT_APP_NEW_MESSENGER_APP_ID;
fjs.parentNode.insertBefore(js, fjs);
})(document, 'script', 'facebook-jssdk');
}
// [NOTE]: This works when using the class component
// the redirect happens
componentWillUpdate = (nextProps, nextState) => {
console.log('nextProps.redirect', nextProps.redirect)
console.log('nextProps', nextProps)
console.log('nextState', nextState)
if (nextProps.redirect !== undefined && nextProps.redirect !== '/') {
this.history.push(nextProps.redirect);
}
};
render() {
const { t } = this.props;
if (this.state.hasFB) {
return (
<>
<Elements stripe={stripePromise}>
<ConnectedRouter history={this.history}>{routes}</ConnectedRouter>
</Elements>
</>
);
} else {
return null;
}
}
}
const mapStateToProps = (state) => {
let isLoading = state.siteR.isLoading ? 'open' : 'close';
let data = {
redirect: state.siteR.location,
isLoading: isLoading,
user: state.userR[state.userR.id],
id: state.userR.id,
};
if (state.userR[state.userR.id] !== undefined) {
data.models = state.userR[state.userR.id].models;
}
return data;
};
const mapDispatchToProps = (dispatch) => {
return {
// ommited_code
};
};
export default compose(
connect(mapStateToProps, mapDispatchToProps),
withTranslation(),
)(App);
Same code, but on a functional component (new code)
const App = (props) => {
console.log('props', props)
// console.log('props.redirect', props.redirect)
const [hasFB, setHasFB] = useState(false)
const [error, setError] = useState(false)
const [redirect, setRedirect] = useState(props.redirect)
const history = createBrowserHistory()
let facebookInterval = undefined
const {t} = useTranslation()
const initializeFacebook = () => {
facebookInterval = setInterval(() => {
if (window.FB !== undefined) {
setHasFB(true)
clearInterval(facebookInterval);
}
}, 100);
window.fbAsyncInit = function () {
window.FB.init({
appId: process.env.REACT_APP_NEW_MESSENGER_APP_ID,
autoLogAppEvents: true,
xfbml: true,
version: 'v3.3',
});
};
(function (d, s, id) {
var js,
fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) {
return;
}
js = d.createElement(s);
js.id = id;
js.src =
'https://connect.facebook.net/en_US/sdk.js?xfbml=1&version=v9.0&appId=' +
process.env.REACT_APP_NEW_MESSENGER_APP_ID;
fjs.parentNode.insertBefore(js, fjs);
})(document, 'script', 'facebook-jssdk');
}
// [NOTE]: I get the right values from props.redirect
// and the state
// the redirect just doesnt happen
const handleRedirect = () => {
if (props.redirect !== undefined && props.redirect !== '/') {
console.log('redirect on setRedirect', redirect)
setRedirect(history.push(props.redirect))
}
}
useEffect(() => {
initializeFacebook()
handleRedirect()
console.log('redirect on setRedirect', redirect)
}, [])
if (hasFB) {
return (
<>
<Elements stripe={stripePromise}>
<ConnectedRouter history={history}>{routes}</ConnectedRouter>
</Elements>
</>
);
} else {
return null;
}
}
const mapStateToProps = (state) => {
let isLoading = state.siteR.isLoading ? 'open' : 'close';
let showModelSelect = state.modelR.showModelSelect ? 'open' : 'close';
let showModelAdd = state.modelR.showModelAdd ? 'open' : 'close';
let data = {
redirect: state.siteR.location,
isLoading: isLoading,
user: state.userR[state.userR.id],
id: state.userR.id,
};
if (state.userR[state.userR.id] !== undefined) {
data.models = state.userR[state.userR.id].models;
}
return data;
};
const mapDispatchToProps = (dispatch) => {
return {
// ommited_code
};
};
export default compose(
connect(mapStateToProps, mapDispatchToProps),
withTranslation(),
)(App);
The routes I'm trying to switch to/from
const routes = (
<div>
<Switch>
<SentryRoute exact path='/' component={UserLogin} />
<SentryRoute exact path='/user' component={Profile} />
{/* // omited_code */}
</Switch>
</div>
)
export default routes;

From the documentation, it looks like you might be able to use the additional params in the connect function.
import { push } from 'connected-react-router';
/* code stuffs */
props.push('/home');
export default connect(null, { push })(Component);
https://github.com/supasate/connected-react-router/blob/master/FAQ.md#with-react-redux

Related

How to use React useContext with leaflet routing machine and react leaflet?

I'm trying to use a useContext hook inside a react-leaflet controlComponent but I have an error when my context fires the update function.
I use a react-leaflet controlComponent because of leaflet routing machine. I think the code + the error are better than word:
MainBoard.tsx
export const CartographyContext: React.Context<CartographyContextType> = React.createContext<CartographyContextType>({ positions: [] });
...
const routeSummaryValueContext = React.useMemo(
() => ({ routeSummary, setRouteSummary }),
[routeSummary]
);
const elevationProfileValueContext = React.useMemo(
() => ({ elevationProfile, setElevationProfile }),
[elevationProfile]
);
........
<CartographyContext.Provider value={{ positions, elevationProfileValueContext, routeSummaryValueContext, positionsValueContext, addPosition, changePosition }}>
.........
<RoutingMachine
orsOptions={{
....
}} />
..........
</CartographyContext.Provider>
RoutingMachine.tsx:
const CreateRoutineMachineLayer = (props: any) => {
const geoService = new GeoLocalisationService();
const cartographyContext: CartographyContextType = React.useContext<CartographyContextType>(CartographyContext);
const [routes, setRoutes] = React.useState<any[]>();
React.useEffect(() => {
if (routes) {
//The line which cause the error
cartographyContext.elevationProfileValueContext.setElevationProfile(geoService.getElevationProfile(decodePolyline(routes[0].geometry, true)));
const summary: RouteSummary = {
ascent: routes[0].routeSummary.ascent,
descent: routes[0].routeSummary.descent,
distance: routes[0].routeSummary.distance,
estimatedDuration: routes[0].routeSummary.duration
}
cartographyContext.routeSummaryValueContext.setRouteSummary(summary);
}
}, [routes]);
const { orsOptions } = props;
const instance = L.Routing.control({
router: new OpenRouteRouter(orsOptions),
lineOptions: {
styles: [{ color: "#3933ff", weight: 4 }],
extendToWaypoints: true,
missingRouteTolerance: 0
},
routeWhileDragging: true,
autoRoute: true,
geocoder: new geocoder.Geocoder(),
}).on('routesfound', (e) => {
setRoutes(e.routes);
});
useMapEvents({
click: (e: L.LeafletMouseEvent) => {
if (instance.getWaypoints().length === 2 && instance.getWaypoints()[0].latLng == null) {
instance.spliceWaypoints(0, 1, new L.Routing.Waypoint(e.latlng, null, {}));
} else if (instance.getWaypoints().length === 2 && instance.getWaypoints()[1].latLng == null) {
instance.spliceWaypoints(1, 1, new L.Routing.Waypoint(e.latlng, null, {}));
} else {
instance.spliceWaypoints(instance.getWaypoints().length, 0, new L.Routing.Waypoint(e.latlng, null, {}));
}
}
});
return instance;
};
const RoutingMachine = createControlComponent(CreateRoutineMachineLayer);
error :
g: React has detected a change in the order of Hooks called by ForwardRef(LeafComponent). This will lead to bugs and errors if not fixed. For more information, read the Rules of Hooks: https://reactjs.org/link/rules-of-hooks
Previous render Next render
------------------------------------------------------
1. useContext useContext
2. useRef useRef
3. useContext useRef
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
..............
Uncaught Error: Rendered fewer hooks than expected. This may be caused by an accidental early return statement.
I clearly doing something wrong here but I haven't found yet.
Thank you
Kind regards
Ok I found the good implementation :
const RoutingMachine: React.FC<RoutingMachineProps> = (props) => {
//const RoutineMachine = (props: any) => {
const geoService = new GeoLocalisationService();
const cartographyContext: CartographyContextType = React.useContext<CartographyContextType>(CartographyContext);
const [instance, setInstance] = React.useState<any>();
const [alreadyDisplayed, setAlreadyDisplayed] = React.useState(false);
const { orsOptions } = props;
const map = useMap();
//const instance = L.Routing.control({
React.useEffect(() => {
const instance = L.Routing.control({
router: new OpenRouteRouter(orsOptions),
lineOptions: {
styles: [{ color: "#3933ff", weight: 4 }],
extendToWaypoints: true,
missingRouteTolerance: 0
},
routeWhileDragging: true,
autoRoute: true,
geocoder: (L.Control as any).Geocoder.google({
apiKey: GOOGLE.googleMapApiKey,
}),
}).on('routesfound', (e) => {
const routes = e.routes;
cartographyContext.setElevationProfile(geoService.getElevationProfile(decodePolyline(routes[0].geometry, true)));
const summary: RouteSummary = {
ascent: routes[0].routeSummary.ascent,
descent: routes[0].routeSummary.descent,
distance: routes[0].routeSummary.distance,
estimatedDuration: routes[0].routeSummary.duration
}
cartographyContext.setRouteSummary(summary);
})
setInstance(instance);
instance.addTo(map);
}, []);
useMapEvents({
click: (e: L.LeafletMouseEvent) => {
if (instance) {
if (instance.getWaypoints().length === 2 && instance.getWaypoints()[0].latLng == null) {
instance.spliceWaypoints(0, 1, new L.Routing.Waypoint(e.latlng, null, {}));
} else if (instance.getWaypoints().length === 2 && instance.getWaypoints()[1].latLng == null) {
instance.spliceWaypoints(1, 1, new L.Routing.Waypoint(e.latlng, null, {}));
} else {
instance.spliceWaypoints(instance.getWaypoints().length, 0, new L.Routing.Waypoint(e.latlng, null, {}));
}
}
}
});
return null;
};
export default RoutingMachine;

Warning: Expected server HTML to contain a matching <div> in <div> when use localStorage as params

I think this warning appear when i work with localStorage, some answers in another page is use useEffect(), but I don't know how to use query or mutation in useEffect().How can I fix it?
export const productListForBill = () : GetProductForBill[] =>{
const returnEmtpyArray : GetProductForBill[] = []
if(typeof window !== "undefined"){
if(localStorage.getItem("products"))
{
const tempProduct = JSON.parse(localStorage.getItem("products") || "")
if(Array.isArray(tempProduct)){
return tempProduct
}
}
}
return returnEmtpyArray
}
const Cart = () => {
const { data } = useGetSomeProductQuery({
variables: { productList: productListForBill() },
});
return (
<>
<Navbar />
{data?.getSomeProduct ? (
data.getSomeProduct.map((product) => (
<div key={`${product.name}-${product.type}`}>
name: {product.name} --|-- type: {product.type} --|-- amount :{" "}
{product.amount} --|-- unitPrice : {product.unitPrice} --|-- total:{" "}
{product.totalPrice}
</div>
))
) : (
<div>Nothing in here</div>
)}
</>
);
};
export const getStaticProps: GetStaticProps = async () => {
const apolloClient = initializeApollo();
await apolloClient.query({
query: GetSomeProductDocument,
variables: { productList: productListForBill() },
});
return addApolloState(apolloClient, {
props: {},
});
};
I have to type something for text checker of Stackoverflow, have a nice day!
code of useGetSomeProductQuery, i'm working with graphql and use codegen to generate it at client
#Query((_return) => [ProductOfBill], { nullable: true })
async getSomeProduct(
#Arg("productList", (_type) => [GetProductForBill])
productList: GetProductForBill[]
): Promise<ProductOfBill[] | null | undefined> {
try {
const newList : ProductOfBill[] = await Promise.all(productList.map(async (product) => {
const price = await Price.findOne({
where: {
type: product.type,
product: product.productId,
}
});
const newProductOfBill = ProductOfBill.create({
name:product.name,
amount:product.amount,
type:product.type,
unitPrice:price?.price
})
return newProductOfBill
}))
.then(list => {
console.log(list)
return list
})
.catch(_ => {
const resultList : ProductOfBill[] = []
return resultList
})
return newList;
} catch (error) {
console.log(error);
return undefined;
}
}

Call data after refreshing the page (React Native Hooks)

Here's my code first
const [getData, setGetData] = useState();
const [ref, setRef] = useState();
const initializeData = async() => {
const userToken = await AsyncStorage.getItem('user_id');
setGetData(JSON.parse(userToken));
}
useEffect(() => {
return initializeData();
},[])
useEffect(() => {
let interval;
if(getData != null)
{
interval = setInterval(() => {
setRef(firestore().collection('**********').where("SendersNo", "==", getData.number));
}, 2000);
}
return () => clearInterval(interval);
},[getData])
useEffect(() => {
if(ref != null)
{
return ref.onSnapshot(querySnapshot => {
const list = [];
querySnapshot.forEach(doc => {
const {
id,driverName,driverContactNumber,driverRating,driverPlateNumber,driverTrackingNumber,userPlaceName,
destinationPlaceName,PaymentMethod,Fare
} = doc.data();
list.push({id: doc.id,driverName,driverContactNumber,driverRating,
driverPlateNumber,driverTrackingNumber,userPlaceName,destinationPlaceName,PaymentMethod,Fare});
});
setUserBookingData(list);
console.log("HEY!");
});
}
},[])
const CurrentTransaction = () => {
if(ref == null)
{
return (
<View>
<Text>You don't have a Current Transaction</Text>
</View>
)
}
else
{
return userBookingData.map((element) => {
return (
<View key={element.id}>
<View>
<Text>{element.name}</Text>
</View>
</View>
)
});
}
}
So currently right now what I am trying to is if there's a data on my firestore it will update on the screen but before updating it I need to get the data from the setGetData so that I can query it but the problem is that when I refresh the whole simulator/page it doesn't get the data but instead just a blank page . But when i edit and save my code without refreshing the page/simulator it can get the data . Can someone help me what I am doing wrong .
EDIT
if I do this
useEffect(() => {
if(ref != null)
{
return ref.onSnapshot(querySnapshot => {
const list = [];
querySnapshot.forEach(doc => {
const {
id,driverName,driverContactNumber,driverRating,driverPlateNumber,driverTrackingNumber,userPlaceName,
destinationPlaceName,PaymentMethod,Fare
} = doc.data();
list.push({id: doc.id,driverName,driverContactNumber,driverRating,
driverPlateNumber,driverTrackingNumber,userPlaceName,destinationPlaceName,PaymentMethod,Fare});
});
setUserBookingData(list);
console.log("HEY!");
});
}
else
{
return null;
}
},[ref])
it keeps looping the console.log('hey') but it can get the data and display it . but it loops so its bad.
i believe snapshot from firebase realtime database is a listener so its doesn't need setinterval
useEffect(() => {
if(getData != null)
{
const ref = firestore().collection('**********').where("SendersNo", "==", getData.number);
ref.onSnapshot(querySnapshot => {
const list = [];
querySnapshot.forEach(doc => {
const {
id,driverName,driverContactNumber,driverRating,driverPlateNumber,driverTrackingNumber,userPlaceName,
destinationPlaceName,PaymentMethod,Fare
} = doc.data();
list.push({id: doc.id,driverName,driverContactNumber,driverRating,
driverPlateNumber,driverTrackingNumber,userPlaceName,destinationPlaceName,PaymentMethod,Fare});
});
setUserBookingData(list);
console.log("HEY!");
});
}
return () => {
//clear your ref listener here
}
},[getData])
if you put a return on use effect it will be called after the screen is no longer used.
useEffect(()=>{
//inside this will be called when the screen complete render
const someListener = DeviceEventEmitter('listentosomething',()=>{
//do something
});
return ()=>{
//inside this will be called after the screen no longer be used
//example go to other screen
someListener.remove();
}
},)

Rendering logger output to component on Tesseract.js (with React) slows down

I would like to add a progress indicator to Tesseract.js logging.
The example in docs works just fine, until setting a state hook into logger:
const worker = createWorker({
logger: (m) => {
setProgress(m) //new
}});
...
const [ocr, setOcr] = useState('Recognizing...');
const [progress, setProgress] = useState(null); //new
...
return (
<div className="App">
<p>
<LogComponent progress={progress}/> //new
</p>
</div>);
This causes the browser to slow down significantly (probably due React's way to re-render on each state update). Is there a way to get around this? Using React.memo perhaps?
You could design it into a hook so your entire component doesn't re-render. Here is a useTesseract hook you can use that I created: https://gist.github.com/KevinDanikowski/25cdcdda2ef4750bcf443f2027cc375a
Copy and Pasted:
import { useState, useEffect } from 'react'
import { createWorker } from 'tesseract.js'
export default function useTesseract({ tesseractLanguage = 'eng', log = false }) {
const [tesseractWorker, setTesseractWorker] = useState(null)
const [loadingModel, setLoadingModel] = useState(true)
const [modelError, setModelError] = useState(false)
const [imgResults, setImgResults] = useState({})
const [processing, setProcessing] = useState(false)
const [progress, setProgress] = useState(0)
const extractTextFromImage = (imageUrl) => {
const recognize = async () => {
const {
data: {
hocr: htmlOutput,
text,
// tsv, box, unlv
},
} = await tesseractWorker.recognize(imageUrl)
setProcessing(false)
setImgResults({ html: htmlOutput, text })
}
if (loadingModel) {
try {
setTimeout(recognize, 400)
} catch (e) {
console.error('Timeout Error:', e.message)
setImgResults({ error: true })
}
} else {
try {
setProcessing(true)
recognize()
} catch (e) {
console.error('Tesseract Error:', e.message)
setProcessing(false)
setImgResults({ error: true })
}
}
}
const logger = (m) => {
setProgress(m.progress)
if (log) {
console.info(m)
}
}
useEffect(() => {
const loadTesseract = async () => {
if (tesseractWorker) {
await tesseractWorker.loadLanguage(tesseractLanguage)
await tesseractWorker.initialize(tesseractLanguage)
console.info(`INFO: loaded ${tesseractLanguage} tesseract model`)
} else {
const tesseractWorker = createWorker({
logger,
// specify paths because sometimes the free CDN goes down
// corePath: '/static/tesseract-core.wasm.2.2.0.js',
// workerPath: '/static/tesseract-worker.v2.1.4.min.js',
})
setTesseractWorker(tesseractWorker)
await tesseractWorker.load()
await tesseractWorker.loadLanguage(tesseractLanguage)
await tesseractWorker.initialize(tesseractLanguage)
console.info(`INFO: loaded ${tesseractLanguage} tesseract model`)
setLoadingModel(false)
setModelError(true)
setLoadingModel(false)
}
}
loadTesseract().catch((e) => {
console.error(`ERROR: Failed to load tesseract model`, e.message)
setModelError(true)
setLoadingModel(false)
})
// TODO: Have to add a ref to reference the latest tesseractWorker in order to terminate
// return () => tesseractWorker.terminate()
}, [tesseractLanguage])
return {
imgResults,
loadingModel,
processing,
modelError,
progress,
extractTextFromImage,
}
}
I managed to implement and render the progress of the tesseract worker by putting my application render inside a class and using the setState method:
class App extends React.Component {
constructor(props){
super(props)
this.state = {
file: null
}
this.handleChange = this.handleChange.bind(this)
}
setProgress(m) {
if (m.progress !== 0 && m.progress !== 0.5 && m.progress !== 1){
var prog = "Progress: " + Math.round(m.progress*100) + "%"
this.setState({progress: prog})
}
}
worker = createWorker({
logger: m => this.setProgress(m),
});
doOCR = async () => {
await this.worker.load();
await this.worker.loadLanguage('eng');
await this.worker.initialize('eng');
const { data: { text } } = await this.worker.recognize(this.state.file);
this.setState({text: extractTotal(text),
progress: ""});
};
handleChange(event) {
this.setState({text: placeholder});
this.setState({
file: URL.createObjectURL(event.target.files[0]),
})
this.doOCR()
}
setText(input){
if (!input) {
return "Please select a receipt"
}
else {
return input
}
}
render() {
console.log("Text: " + this.state.text)
return (
<div className="container">
<p>{this.setText(this.state.text)}</p>
<p>{this.state.progress}</p>
<input type="file" onChange={this.handleChange}/>
<img src={this.state.file} className='logo' alt=""/>
</div>
);
}
}

HOC is triggering while I use it on container

Could anyone can help me, because i can't understand why HOC is causing ifnite loop while I am using it on container. That's my container:
class UserContainer extends Component {
componentDidMount() {
const { onValuePassedThroughParams, match } = this.props;
const { user } = match.params;
if (user !== '') {
onValuePassedThroughParams(user);
}
}
render() {
const { user } = this.props;
return (
<UserView user={user} />
);
}
}
const UserContainerWithLoading = LoaderHOC(UserContainer);
const mapStateToProps = state => ({
user: state.user,
});
const mapDispatchToProps = dispatch => ({
onValuePassedThroughParams: val => dispatch(takeUserNameAndFetchData(val)),
});
export default
withRouter(
connect(mapStateToProps, mapDispatchToProps)(UserContainerWithLoading),
);
my HOC:
const LoaderHOC = WrappedComponent => props =>{
return(
props.user.isLoading
? <div className={styles.ldsHourglass} />
: <WrappedComponent {...props} />
)};
and also a thunk:
function fetchData(url) {
return (
fetch(url)
.then(result => result.json())
);
}
export default function takeUserNameAndFetchData(name) {
const userInfoUrl = `https://api.github.com/users/${name}`;
const userRepoUrl = `https://api.github.com/users/${name}/repos`;
return (dispatch) => {
dispatch(fetchUserBegin());
Promise.all([
fetchData(userInfoUrl),
fetchData(userRepoUrl),
])
.then(([info, repos]) => {
dispatch(fetchUserInfoSucces(info));
dispatch(fetchUserReposSuccess(repos));
dispatch(fetchUserLoadingEnd());
})
.catch((err) => {
console.log(`ERROR!${err}`);
dispatch(fetchUserError());
});
};
}
When I use an HOC on my View component everything is fine and it's stop geting data from the server, but when I am using it on container there is always an infinite loop. Do you have any advice for me?

Resources