Isomorphically render html string with Koa - rendering

I'm trying to get Koa to isomorphically render a html string it receives from react-router.
Here's the code I've been trying to make work:
server.js
import koa from 'koa';
import React from "react";
import Router from "react-router";
import routes from "./routes";
const server = koa();
const port = process.env.NODE_ENV || 8000;
server.use(function *() {
try {
yield Router.run(routes, this.request.url, function (Handler) {
var content = React.renderToString(<Handler/>)
this.body = content
})
}
catch (err) {
this.status = err.status || 500;
this.body = err.message;
this.app.emit('error', err, this);
}
})
server.listen(port, function(err) {
if (err) throw err;
console.log("Server running on: http://localhost:"+port)
})
routes.js
import React from "react"
import {Route, DefaultRoute} from "react-router"
import Main from "./components/main"
export default (
<Route path="/">
<DefaultRoute handler={Main} name="main" />
</Route>
)
main.js
import React from "react"
const Main = React.createFactory(React.createClass ({
render () {
return (
<div>HELLO</div>
)
}
}))
export default Main
Getting several errors:
Warning: Component(...): No render method found on the returned
component instance: you may have forgotten to define render in your
component or you may have accidentally tried to render an element
whose type is a function that isn't a React component.
Warning: Don't set the props property of the React element. Instead,
specify the correct value when initially creating the element.
TypeError: Can't add property context, object is not extensible
Warning: Something is calling a React component directly. Use a
factory or JSX instead. See: https://fb.me/react-legacyfactory

for those who needs an answer with this, hope this will help you guys.
server.js
import koa from 'koa';
import React from "react";
import Router from "react-router";
import routes from "./routes";
const server = koa();
let handler;
server.use(function *(next) {
Router.run(routes, this.request.url, (Handler) => {
handler = React.renderToString(<Handler/>);
});
yield next;
});
server.use(function *() {
this.body = handler;
});
export default server;
components/index.js
import React from "react";
const Main = React.createClass ({
render () {
return (
<div>HELLO</div>
);
}
});
export default Main;
i dont see the need to create factory for this one.

I was getting the "calling a React component directly" warning too and fixed it by yielding a promise
function *() {
this.body = yield new Promise(resolve => {
Router.run(routes, this.req.url, Handler => {
resolve(React.renderToString(<Handler/>));
});
});
}

Related

Context API dispatch not called with onEffect while using expo-splash-screen

When I am trying to use the dispatch function recieved with the useContext hook I cannot get the change the content of the data inside the context. It looks like as if the call wasn't even made, when I try to log something inside the conext's reducer it doesn't react. When I try to call it from other components, it works just fine.
Sorry if it's not clean enough, I'm not too used to ask around here, if there's anything else to clarify please tell me, and I'll add the necessary info, I just don't know at the moment what could help.
import { QueryClient, QueryClientProvider } from "react-query";
import LoginPage from "./src/pages/LoginPage";
import { UserDataContext, UserDataProvider } from "./src/contexts/UserData";
import { useState } from "react";
import AsyncStorage from "#react-native-async-storage/async-storage";
import { useContext } from "react";
import * as SplashScreen from "expo-splash-screen";
import { useEffect } from "react";
import { useCallback } from "react";
import { UserData } from "./src/interfaces";
SplashScreen.preventAutoHideAsync();
const queryClient = new QueryClient();
export default function App() {
const [appReady, setAppReady] = useState<boolean>(false);
const { loggedInUser, dispatch } = useContext(UserDataContext);
useEffect(() => {
async function prepare() {
AsyncStorage.getItem("userData")
.then((result) => {
if (result !== null) {
console.log(loggedInUser);
const resultUser: UserData = JSON.parse(result);
dispatch({
type: "SET_LOGGED_IN_USER",
payload: resultUser,
});
new Promise((resolve) => setTimeout(resolve, 2000));
}
})
.catch((e) => console.log(e))
.finally(() => setAppReady(true));
}
if (!appReady) {
prepare();
}
}, []);
const onLayoutRootView = useCallback(async () => {
if (appReady) {
await SplashScreen.hideAsync();
}
}, [appReady]);
if (!appReady) {
return null;
}
return (
<>
<UserDataProvider>
<QueryClientProvider client={queryClient}>
<LoginPage onLayout={onLayoutRootView} />
</QueryClientProvider>
</UserDataProvider>
</>
);
}
I'm thinking I use the context hook too early on, when I check the type of the dispatch function here it says it's [Function dispatch], and where it works it's [Function bound dispatchReducerAction].
I think the problem might come from me trying to call useContext before the contextprovider could render, but even when I put the block with using the dispatch action in the onLayoutRootView part, it didn't work.

In Nextjs, how to extract what's in the location bar?

I'm following this guide to learn how to implement a Login with Google button (along with the help of Strapi. The guide works fine with Reactjs.
A problem occurs when I try to do the same thing with Nextjs with this part of the code:
import React, { useState, useEffect } from 'react'
import { useLocation } from 'react-router-dom'
import axios from 'axios'
function GoogleAuthCallback() {
const [auth, setAuth] = useState()
const location = useLocation()
useEffect(() => {
if (!location) {
return
}
const { search } = location
axios({
method: 'GET',
url: `http://localhost:1337/auth/google/callback?${search}`,
})
.then((res) => res.data)
.then(setAuth)
}, [location])
return (
<div>
{auth && (
<>
<div>Jwt: {auth.jwt}</div>
<div>User Id: {auth.user.id}</div>
<div>Provider: {auth.user.provider}</div>
</>
)}
</div>
)
}
export default GoogleAuthCallback
I get the following error:
**Question: is there someway to extract what's in the location bar in Nextjs because useLocation() is not working.
Thanks!
I guess that useLocation() try to access window.location, that is not defined in SSR.
You should import your component dynamically with SSR disabled :
for example in your page :
import dynamic from 'next/dynamic'
const DynamicComponentWithNoSSR = dynamic(
() => import('../components/GoogleAuthCallback'), // <- path of your component
{ ssr: false }
)
Please note that you can also use default next.js router :
import { useRouter } from 'next/router'
function GoogleAuthCallback() {
const [auth, setAuth] = useState()
const router = useRouter()
const location = router.pathname // or router.asPath
....
}
The answer is simply you have to use router instead of location to update your component eg:
import {useRouter} from "next/router"
const router = useRouter();
useEffect(() => {'something happened here'},[router])

How to use navigation in this function from importing a hook within this function? any idea?

I am using a function to call an api and i have added navigation on 401 and i want to use navigation here. But as the hooks can be called from the component only. So anybody can tell me how can i add navigation here. So someone i can import here and use while passing in the function.
const GetApiRequestWithToken = async (url, params, headers) => {
return new Promise((resolve, reject) => {
axios.get(base_url_address + url, { headers: headers }).then(resp => {
if (resp.status == 401) {
UnAuthorizedLogout()
} else {
resolve(resp)
}
}).catch((error) => {
resolve(error.response)
});
})
}
So this is a function and how can i import navigation in this. from hooks or some other way.
As this is not allowing to import here
import { useNavigation } from '#react-navigation/native';
and i don't want that everytime, i call GetApiRequestWithToken then pass navigation to it.
You can use a navigation provider pattern to achieve this. You can create a utility file like the following:
// util/navigation.js
import React from 'react';
export const navigationRef = React.createRef();
export const navigate = (routeName, params) => {
navigationRef.current?.navigate(routeName, params);
};
This stores the navigator reference in a local variable. You can see that it requires the navigator object to be passed in by an external component. I would recommend calling this function in your top-level navigation stack component. You most likely already have a component that looks something like the below:
// NavigationContainer.js
import React from 'react';
import { createStackNavigator } from '#react-navigation/stack';
import { NavigationContainer } from '#react-navigation/native';
import { navigationRef } from 'util/navigation'; // from util
import ExampleStack from './ExampleStack';
const Stack = createStackNavigator();
export default () => {
return (
<NavigationContainer ref={navigationRef}> {/* store ref */}
<Stack.Navigator
initialRouteName="Example"
...
>
<Stack.Screen
name="Example"
component={ExampleStack}
/>
</Stack.Navigator>
</NavigationContainer>
);
};
You can see here that the component created by createNavigationContainer will pass the ref to your navigation provider util above.
Finally, you can use the NavigationContainer created in this second file inside your App.js.
// App.js
import NavigationContainer from './NavigationContainer';
export default () => (
<NavigationContainer />
);
Any other functions of the navigator can be added to navigationUtil.js now, for example goBack. You can now use the utility in your axios request like so:
// axios util
import { navigate } from './navigationUtil.js'; // new
const GetApiRequestWithToken = async (url, params, headers) => {
return new Promise((resolve, reject) => {
axios.get(base_url_address + url, { headers: headers }).then(resp => {
if (resp.status == 401) {
navigate('UnauthorizedLogoutScreen'); // new
UnAuthorizedLogout();
} else {
resolve(resp)
}
}).catch((error) => {
resolve(error.response)
});
})
}
I hope this is clear, feel free to ask if something has not been covered.
I think this can be done using a custom hook.
import React, { useEffect } from 'react';
import { useNavigation } from '#react-navigation/native';
import axios from 'axios';
export default function useFetchAPI(url, params, headers) {
const [result, setResult] = React.useState(null);
const navigation = useNavigation();
useEffect(() => {
axios
.get(base_url_address + url, { headers: headers })
.then((resp) => {
console.log('resp: ', resp);
if (resp.status == 401) {
// this will navigate to your UnAuthorizedLogout page
navigation.navigate('UnAuthorizedLogout');
} else {
// otherwise, set the response to result state
setResult(resp);
}
})
.catch((error) => {
setResult(error.response);
});
}, [url])
return result;
}

How to use useNavigation() Hook in Redux actions React Native

I want to navigate user from my redux actions. For example when they
click on login, they navigate from action
.
Two ways i have tried.
1.pass navigation prop from component to action. (it works fine.)
2. use useNavigation() hook in redux actions. (it is not working. (Hooks can only be called inside of the body of a function component)).
Here is my code
action.js
export const registerUser = (data) => {
const navigation = useNavigation()
return async dispatch => {
dispatch(authLoading());
try {
const res = await axios.post(
`${BASE_URL}/mobilesignup`,
data,
{
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
},
);
console.log(res);
dispatch(registerSuccess(res));
navigation.navigate('dashboard')
} catch (err) {
dispatch(authFailed(err));
}
};
};
This code is not working
Error (Hooks can only be called inside of the body of a function
component)
Can anybody help me how can i use useNavigation() in redux actions ?
Thanks
You will have to use the Navigation ref which is there for purposes like calling from the reducer
The idea is to create a navigation.js and set the reference of navigation container and use it.
Code would be like below. (A sample from documentation)
//App.js
import { NavigationContainer } from '#react-navigation/native';
import { navigationRef } from './RootNavigation';
export default function App() {
return (
<NavigationContainer ref={navigationRef}>{/* ... */}</NavigationContainer>
);
}
// RootNavigation.js
import * as React from 'react';
export const navigationRef = React.createRef();
export function navigate(name, params) {
navigationRef.current?.navigate(name, params);
}
You can simply import the navigation js anywhere and call navigate
Documentation
https://reactnavigation.org/docs/navigating-without-navigation-prop/#handling-initialization
According to the documentation of react-navigation v6.x
Define your rootNavigation module as followed:
// RootNavigation.ts
import { createNavigationContainerRef } from "#react-navigation/native";
const navigationRef = createNavigationContainerRef();
export class RootNavigation {
static navigate(name: string, params: any = {}) {
if (navigationRef.isReady()) {
navigationRef.navigate(name, params);
}
}
static get ref():any {
return navigationRef;
}
}
Pass the reference to NavigationContainer located at the root of your App.
// App.js
import { NavigationContainer } from '#react-navigation/native';
import { navigationRef } from './RootNavigation';
export default function App() {
return (
<NavigationContainer ref={RootNavigation.ref}>{/* ... */}</NavigationContainer>
);
}
Then simply use it at an action creator
// any js module
// ...
RootNavigation.navigate('ChatScreen', { userName: 'Lucy' });

How to listen to route changes in react router v4?

I have a couple of buttons that acts as routes. Everytime the route is changed, I want to make sure the button that is active changes.
Is there a way to listen to route changes in react router v4?
I use withRouter to get the location prop. When the component is updated because of a new route, I check if the value changed:
#withRouter
class App extends React.Component {
static propTypes = {
location: React.PropTypes.object.isRequired
}
// ...
componentDidUpdate(prevProps) {
if (this.props.location !== prevProps.location) {
this.onRouteChanged();
}
}
onRouteChanged() {
console.log("ROUTE CHANGED");
}
// ...
render(){
return <Switch>
<Route path="/" exact component={HomePage} />
<Route path="/checkout" component={CheckoutPage} />
<Route path="/success" component={SuccessPage} />
// ...
<Route component={NotFound} />
</Switch>
}
}
To expand on the above, you will need to get at the history object. If you are using BrowserRouter, you can import withRouter and wrap your component with a higher-order component (HoC) in order to have access via props to the history object's properties and functions.
import { withRouter } from 'react-router-dom';
const myComponent = ({ history }) => {
history.listen((location, action) => {
// location is an object like window.location
console.log(action, location.pathname, location.state)
});
return <div>...</div>;
};
export default withRouter(myComponent);
The only thing to be aware of is that withRouter and most other ways to access the history seem to pollute the props as they de-structure the object into it.
As others have said, this has been superseded by the hooks exposed by react router and it has a memory leak. If you are registering listeners in a functional component you should be doing so via useEffect and unregistering them in the return of that function.
v5.1 introduces the useful hook useLocation
https://reacttraining.com/blog/react-router-v5-1/#uselocation
import { Switch, useLocation } from 'react-router-dom'
function usePageViews() {
let location = useLocation()
useEffect(
() => {
ga.send(['pageview', location.pathname])
},
[location]
)
}
function App() {
usePageViews()
return <Switch>{/* your routes here */}</Switch>
}
You should to use history v4 lib.
Example from there
history.listen((location, action) => {
console.log(`The current URL is ${location.pathname}${location.search}${location.hash}`)
console.log(`The last navigation action was ${action}`)
})
withRouter, history.listen, and useEffect (React Hooks) works quite nicely together:
import React, { useEffect } from 'react'
import { withRouter } from 'react-router-dom'
const Component = ({ history }) => {
useEffect(() => history.listen(() => {
// do something on route change
// for my example, close a drawer
}), [])
//...
}
export default withRouter(Component)
The listener callback will fire any time a route is changed, and the return for history.listen is a shutdown handler that plays nicely with useEffect.
import React, { useEffect } from 'react';
import { useLocation } from 'react-router';
function MyApp() {
const location = useLocation();
useEffect(() => {
console.log('route has been changed');
...your code
},[location.pathname]);
}
with hooks
With hooks:
import { useEffect } from 'react'
import { withRouter } from 'react-router-dom'
import { history as historyShape } from 'react-router-prop-types'
const DebugHistory = ({ history }) => {
useEffect(() => {
console.log('> Router', history.action, history.location)
}, [history.location.key])
return null
}
DebugHistory.propTypes = { history: historyShape }
export default withRouter(DebugHistory)
Import and render as <DebugHistory> component
import { useHistory } from 'react-router-dom';
const Scroll = () => {
const history = useHistory();
useEffect(() => {
window.scrollTo(0, 0);
}, [history.location.pathname]);
return null;
}
With react Hooks, I am using useEffect
import React from 'react'
const history = useHistory()
const queryString = require('query-string')
const parsed = queryString.parse(location.search)
const [search, setSearch] = useState(parsed.search ? parsed.search : '')
useEffect(() => {
const parsedSearch = parsed.search ? parsed.search : ''
if (parsedSearch !== search) {
// do some action! The route Changed!
}
}, [location.search])
in this example, Im scrolling up when the route change:
import React from 'react'
import { useLocation } from 'react-router-dom'
const ScrollToTop = () => {
const location = useLocation()
React.useEffect(() => {
window.scrollTo(0, 0)
}, [location.key])
return null
}
export default ScrollToTop
In some cases you might use render attribute instead of component, in this way:
class App extends React.Component {
constructor (props) {
super(props);
}
onRouteChange (pageId) {
console.log(pageId);
}
render () {
return <Switch>
<Route path="/" exact render={(props) => {
this.onRouteChange('home');
return <HomePage {...props} />;
}} />
<Route path="/checkout" exact render={(props) => {
this.onRouteChange('checkout');
return <CheckoutPage {...props} />;
}} />
</Switch>
}
}
Notice that if you change state in onRouteChange method, this could cause 'Maximum update depth exceeded' error.
For functional components try useEffect with props.location.
import React, {useEffect} from 'react';
const SampleComponent = (props) => {
useEffect(() => {
console.log(props.location);
}, [props.location]);
}
export default SampleComponent;
For React Router v6 & React Hooks,
You need to use useLocation instead of useHistory as it is deprecated
import { useLocation } from 'react-router-dom'
import { useEffect } from 'react'
export default function Component() {
const history = useLocation();
useEffect(() => {
console.log('> Router', history.pathname)
}, [history.pathname]);
}
With the useEffect hook it's possible to detect route changes without adding a listener.
import React, { useEffect } from 'react';
import { Switch, Route, withRouter } from 'react-router-dom';
import Main from './Main';
import Blog from './Blog';
const App = ({history}) => {
useEffect( () => {
// When route changes, history.location.pathname changes as well
// And the code will execute after this line
}, [history.location.pathname]);
return (<Switch>
<Route exact path = '/' component = {Main}/>
<Route exact path = '/blog' component = {Blog}/>
</Switch>);
}
export default withRouter(App);
I just dealt with this problem, so I'll add my solution as a supplement on other answers given.
The problem here is that useEffect doesn't really work as you would want it to, since the call only gets triggered after the first render so there is an unwanted delay.
If you use some state manager like redux, chances are that you will get a flicker on the screen because of lingering state in the store.
What you really want is to use useLayoutEffect since this gets triggered immediately.
So I wrote a small utility function that I put in the same directory as my router:
export const callApis = (fn, path) => {
useLayoutEffect(() => {
fn();
}, [path]);
};
Which I call from within the component HOC like this:
callApis(() => getTopicById({topicId}), path);
path is the prop that gets passed in the match object when using withRouter.
I'm not really in favour of listening / unlistening manually on history.
That's just imo.