I've used Redux Promise, but it seems Redux Promise Middleware has more functionality like dispatching multiple actions with "PENDING" or "FULFILLED" appended.
Why would I use one over the other?
I personally prefer Redux Promise Middleware as middleware as it enables optimistic updates; dispatches pending, fulfilled and rejected actions; and works well with with Redux Thunk to chain async actions.
For example, you can use actions with _PENDING and _FULFILLED in reducers and update the UI with progress bar and similar.
There is an alternative to Redux Pomise Middleware. Redux Auto has the same API as redux-promise-middleware and comes with a bunch of utility patterns under the hood to reduce the boilerplate you need to write.
The idea is to have each action in a specific file. co-locating the server call in the file with reducer functions for "pending", "fulfilled" and "rejected". This makes handling promises very easy.
It also automatically attaches a helper object (called "async") to the prototype of your state, allowing you to track in your UI, requeste transitions.
Example:
data/serverThing.js
export function pending (posts, payload){
return posts
}
export function fulfilled (posts, payload, result){
return result.serverResponse
}
export function rejected (posts, payload, error){
return posts;
}
export function action (payload) {
return Promise.resolve({serverResponse: [1,2,3,4]})
}
UI
import React from "react"
import actions from 'redux-auto'
import { connect } from 'react-redux'
class Foobar extends Component {
const currentLoadingData = this.props.data.async.serverThing;
if(currentLoadingData instanceof Error){
var data = currentLoadingData.message
} else if(true === currentLoadingData ){
var data = "Loading..."
} else {
var data = this.porps.data.join();
}
render () {
return (
<div>
<button onClick={()=>actions.data.serverThing()}>Do something!</button>
{ data }
</div>
)
}
}
const mapStateToProps = ( { data }) => {
return { data }
};
export default connect( mapStateToProps )(Foobar);
To understand the above source. redux-auto automatically creates actions and wires them to reduces based on the file structure. Where the folder name becomes the name of the property on the state. The files within a folder are actions to be performed on that part of the state.
Here is a complete redux-auto: helloworld project
Related
I got a problem with my dynamic route. It look like this
[lang]/abc
I am trying to get query value from [lang] but when I using useRouter/withRouter i got query during 2-3 render of page ( on first i got query.lang = undefined ). its possible to get in 1 render or use any technique ?
I found something:
isReady: boolean - Whether the router fields are updated client-side and ready for use. Should only be used inside of useEffect methods and not for conditionally rendering on the server.
https://nextjs.org/docs/api-reference/next/router#router-object
And the code would be like:
const router = useRouter();
useEffect(()=>{
if(!router.isReady) return;
// codes using router.query
}, [router.isReady]);
It's impossible to get the query value during the initial render.
Statically optimized pages are hydrated without the route parameters, so the query is an empty object ({}).
Next.js will populate the query after the page has been hydrated.
Next.js 10.0.5 and up
To determine if the route params are ready, you can use router.isReady inside a useEffect hook. For an example, see the answer provided by #doxylee.
Before Next.js 10.0.5
At first render of a dynamic route router.asPath and router.route are equal. Once query object is available, router.asPath reflects it.
You can rely on the query value within a useEffect hook after asPath has been changed.
const router = useRouter();
useEffect(() => {
if (router.asPath !== router.route) {
// router.query.lang is defined
}
}, [router])
GitHub Issue - Add a "ready" to Router returned by "useRouter"
In NextJS 9+, one way to ensure route parameters are immediately available for page components is to get them from the context arg passed to getServerSideProps() and pass to the component as props.
For a page like [id].js,
export function getServerSideProps(context) {
return {
props: {params: context.params}
};
}
export default ({params}) => {
const {id} = params;
return <div>You opened page with {id}</div>;
};
This is a great question and one that took a few days for me to figure out what the best approach is.
I have personally found three viable solutions to the problem of validating dynamic route path params or even just route path params in general.
The three solutions are
SSR (don't recommend) [Next >= 10]
useRouter
Middleware [Next 12 required]
In my examples a will use a route that requires a reset-token or it should be redirected.
SSR
Firstly server side rending with getServerSideProps.
Vercel recommends to use SSR as a last resort and I would highly recommend not using SSR when able (time to byte & cost).
We suggest trying Incremental Static Generation or Client-side Fetching and see if they fit your needs.
https://vercel.com/blog/nextjs-server-side-rendering-vs-static-generation
But in the case that you do, say there is some server side api validation call you require to validate the query param.
export const getServerSideProps = async (context) => {
const { token } = context.query;
if (!token) {
return {
redirect: {
permanent: false,
destination: "/",
}
}
}
return {
props: {}
// props: { token }
// You could do this either with useRouter or passing props
}
}
useRouter Secondly the easiest useRouter. When I first did this I came across the problem when nextjs/react hydrates there will be a point when the query params are null. Luckily useRouter has isReady!
import Router, { useRouter } from "next/router";
const { query, isReady } = useRouter();
useEffect(() => {
if (!isReady) return;
if (!query.token) {
Router.push("/")
}
}, [isReady])
Middleware now this is my personal favourite as it seperates the functionality in a clean way imo.
I found this based of a vercel example. I would highly recommend reading through a bunch of these to find best practices.
https://github.com/vercel/examples/
import { NextResponse, NextRequest } from 'next/server'
export async function middleware(req) {
const { pathname, searchParams } = req.nextUrl
if (pathname == '/reset-token') {
const index = searchParams.findIndex(x => x.key === "token")
// You could also add token validation here.
if (!index) {
return NextResponse.redirect('/')
}
}
return NextResponse.next()
}
Here is the repo which has some cool filtering of query parameters.
This is a more soft approach instead of hard redirecting.
https://github.com/vercel/examples/tree/main/edge-functions/query-params-filter
Nico also has a great answer on this, expect I wouldn't recommend using hooks like in his example, instead use isReady.
https://stackoverflow.com/a/58182678/4918639
For Class Component Lovers
The even better approach is to listen for a dedicated event for this routeChangeComplete using this.props.router.events.on method, inside componentDidMount if you're using class component -
routeChangeComplete = () => {
// this WILL have valid query data not empty {}
console.log(this.props.router.query);
};
componentDidMount() {
this.props.router.events.on("routeChangeComplete", this.routeChangeComplete);
}
componentWillUnmount() {
this.props.router.events.off("routeChangeComplete", this.routeChangeComplete);
}
Ref: https://nextjs.org/docs/api-reference/next/router#routerevents
routeChangeComplete: Fires when a route changed completely.
Practically when isReady has become true or when router.query object has data.
For NextJS version - 12.0.8
"If you export a function called getServerSideProps (Server-Side Rendering) from a page, Next.js will pre-render this page on each request using the data returned by getServerSideProps."
=async functions
refference:https://nextjs.org/docs/basic-features/data-fetching/get-server-side-props#getserversideprops
Simply putting that async function on the page notifies NextJS of its presence.During prerendering stage of the component, the query object of the router will be empty.
isReady: boolean - Whether the router fields are updated client-side and ready for use. Should only be used inside of useEffect methods and not for conditionally rendering on the server.
refference: https://nextjs.org/docs/api-reference/next/router
solution:
import { useRouter } from 'next/router';
const Fn = () =>{
const router = useRouter();
const { param } = router.query;
const fetchData = async () => {
await fetch();
}
useEffect(() => {
fetchCat();
}, [router.isReady]);
}
I resolved my problem that I need it in Hoc component.
I wrapped using withRouter(withLocale(Comp)) and create conditional in HOC
export default function withLocale(WrappedPage) {
const WithLocale = ({ router, ...props }) => {
const { lang } = router.query;
if (!lang || !isLocale(lang)) {
return <Error statusCode={404} />;
}
return (
<LocaleProvider lang={lang}>
<WrappedPage {...props} />
</LocaleProvider>
);
};
return WithLocale;
}
Next.js <= 10.0.5
This is a good work around, I found around from this comment
Add useQuery.ts helper file
// useQuery.js
import { useRouter } from 'next/router';
// Resolves query or returns null
export default function useQuery() {
const router = useRouter();
const ready = router.asPath !== router.route;
if (!ready) return null;
return router.query;
}
usage
// In your components (instead of useRouter)
const query = useQuery();
useEffect(() => {
if (!query) {
return;
}
console.log('my query exists!!', query);
}, [query]);
Class Component | 12/16/2022 | React JS 18.2.0 | Next JS 13.0.6
I got the answer for those who want to use Class Component. This was actually nowhere to be found ! Enjoy !
You will add if(this.props.router.isReady) and include return in the condition in render().
.
.
import { withRouter } from 'next/router';
import { Component } from 'react';
class User extends Component {
...
render() {
if(this.props.router.isReady){ // Add this condition and include return ()
// Do anything
console.log(this.props.router.query) // Log 'query' on first render
return (
<div>
<SearchBar pid={this.props.router.query.pid} /> // Pass the query params to another component if needed
</div>
);
}
}
}
export default withRouter(User);
All articles I have read on promises show examples with console.log - I am using AWS Athena and want to display the result on the webpage in my React export. The react export does not allow the use of .then. So I need to resolve the promise to an external variable.
client is a aws athena client which returns a promise I need to resolve.
async function getResult(){
try {
return await client.send(command);
} catch (error) {
return error.message;
}
}
export default getResult()
I want to display the result in App.js
render()
{
return (
{ athena }
)
It displays in the console but not on the webpage as the page is loaded before the variable is resolved.
More complete example of App.js
import athena from './athena';
class App extends Component {
render()
{
let athena_result = athena.then(function(result) {
console.log(result)
}
)
return ( athena_result )
Causes Error
Error: Objects are not valid as a React child (found: [object Promise])
The render method of all React components is to be considered a pure, synchronous function. In other words, there should be no side effects, and no asynchronous logic. The error Error: Objects are not valid as a React child (found: [object Promise]) is the component attempting to render the Promise object.
Use the React component lifecycle for issuing side-effects. componentDidMount for any effects when the component mounts.
class App extends Component {
state = {
athena: null,
}
componentDidMount() {
athena.then(result => this.setState({ athena: result }));
}
render() {
const { athena } = this.state;
return athena;
}
}
If you needed to issue side-effects later after the component is mounted, then componentDidUpdate is the lifecycle method to use.
Class components are still valid and there's no plan to remove them any time soon, but function components are really the way going forward. Here's an example function component version of the code above.
const App = () => {
const [athenaVal, setAthenaVAl] = React.useState(null);
React.useEffect(() => {
athena.then(result => setAthenaVAl(result));
}, []); // <-- empty dependency array -> on mount/initial render only
return athenaVal;
}
The code is a little simpler. You can read more about React hooks if you like.
You can use a state, and just set the state to the response's value when it's done:
const Component = () => {
const [athena, setAthena] = useState(""); // Create a state with an empty string as initial value
// Send a request and on success, set the state to the response's body, and on fall set the state to the error message
useEffect(() => client.send(command).then((response) => setAthena(response.data)).catch((error) => setAthena(error.message)), []);
return <>{athena}</>;
};
Im trying to add a simple common action in vuex that returns a bool,but it looks like it doesn't return what I need.
My child component call:
<ArrowDownIcon
class="campaigns-grid__header-direction"
v-if="isOrderedBy(column.id)"
class="{'campaigns-grid__header-direction--asc': order.isDescending}"
/>
My imports and vuex call on root component:
import { createNamespacedHelpers } from 'vuex'
const { mapActions } = createNamespacedHelpers('campaigns')
export default {
methods: {
...mapActions(['isOrderedBy', 'orderBy'])
}
}
My vuex module ('campaigns'):
export default {
namespaced: true,
actions: {
isOrderedBy (column) {
if (column === 'test') {
return true
}
return false
},
}
}
As much as this might possibly work, You shouldn't use Vuex Actions to return Boolean values since Vuex actions return promises. The ideal process of working with vuex is:
-> dispatch Action -> Action makes Network calls -> Actions Commits Data to State -> Getters Pull data from State
To Solve this issue use Mixins, Since you're trying to make the isOrderedBy(..) function available application wide, and you also want it to return a boolean value depending on the provided argument.
Create a mixin folder if you don't already have one, and add an orderMixin.js file: the file should contain the isOrderedBy() function as a method.
export const orderMixin = {
methods: {
isOrderedBy(column) {
return column === 'test'
}
}
}
Then within your component import the mixin and use it.
<template>
...
<ArrowDownIcon
class="campaigns-grid__header-direction"
v-if="isOrderedBy(column.id)"
class="{'campaigns-grid__header-direction--asc': order.isDescending}"
/>
...
</template>
<script>
import { orderMixin } from 'path-to-mixins/orderMixin'
export default {
mixins: [orderMixin],
...
}
</script>
In vuex action returns promise.
So if you want to get return value from the action you can do like
if (await isOrderedBy()) {
console.log('isOrderBy returns true')
} else {
console.log('isOrderBy returns false')
}
I am using react-localize-redux for my multilingual application and MySQL to fetch data. One of my actions needs locale data to pass it to backend as an argument so that backend responds with proper data. But by the time locale is available, action gets called and application crashes, how can I resolve the issue?
Here is code:
import React, { Component } from 'react'
import RestaurantCard from './RestaurantCard';
import {Row} from 'react-bootstrap';
import { connect } from 'react-redux';
import {getAllRestaurants} from "../actions/restaurantActions";
import { withLocalize } from 'react-localize-redux';
class RestaurantCardColumns extends Component {
constructor(props){
super(props);
}
componentDidMount(){
this.props.getAllRestaurants(this.props.activeLanguage);
}
render() {
if(this.props.loading || this.props.restaurants === null){
return <p>Loading...</p>
} else {
return (
<Row>
<RestaurantCard data = {this.props.restaurants[0]}/>
<RestaurantCard data = {this.props.restaurants[1]}/>
<RestaurantCard data = {this.props.restaurants[2]}/>
<RestaurantCard data = {this.props.restaurants[3]}/>
</Row>)
}
}
}
const mapStateToProps = (state) =>{
return {
auth: state.auth,
errors: state.errors,
restaurants: state.restaurData.restaurants,
loading: state.restaurData.loading
}
}
export default connect(mapStateToProps, {getAllRestaurants})(withLocalize(RestaurantCardColumns));
My problem is in this particular line:
this.props.getAllRestaurants(this.props.activeLanguage);
When I debug I can see that activeLanguage is available in render() lifecycle.
How can I await for that property before calling getAllRestaurants
Check for availability of this.props.activeLanguage before fetching data. Trigger fetching data once activeLanguage is available. And finally ensure that fetching happening only once (if you need)
class RestaurantCardColumns extends Component {
constructor(props){
super(props);
this.didFetch = false; // store outside of state to not trigger rendering
}
componentDidMount(){
this.fetchAllRestaurants();
}
componentDidUpdate(prevProps) {
if (prevProps.activeLanguage !== this.props.activeLanguage) {
this.fetchAllRestaurants();
}
}
fetchAllRestaurants() {
if (!!this.props.activeLanguage && !this.didFetch) {
this.props.getAllRestaurants(this.props.activeLanguage);
this.didFetch = true;
}
}
Be aware that this approach is entirely relied on the component's existence, i.e. if the component is not in virtual DOM, the API call will not happen. You should consider trigger the call using a redux's middleware, like redux-thunk or redux-saga as other people in here suggest.
Use a store enhancer middleware like Thunk. You seem to be making an async request,and store enhancers enable you to make async calls and retrieve data from backend. Middlewares like Thunk stops default action dispatch, perform async requests ad call the dispatch to pass the action along with the updated payload to the reducer. Using proper async - await in the componentDidMount will handle this as well, but store enhancers actually handle that for you.
Here's an example:
async componentDidMount() {
await this.props.getAllRestaurants(this.props.activeLanguage);
}
ComponentDidMount should be an async function, and you should await for
getAllRestaurants to complete.
In addition to that, you should have a local state variable (e.g. IsLoading), that indicates that data is not ready. After the 'await
getAllRestaurants' statement, you set isLoading to falase.
Render will check this local state in order to display a spinner or the data itself, or an error message, if getAllRestaurants fails (in addition to checking isLoading, you should check the redux store, where you will store not only the data, but also a variable indicating whether getAllRestaurants succeeded or failed).
I have a pretty simple set of react components:
container that hooks into redux and handles the actions, store subscriptions, etc
list which displays a list of my items
new which is a form to add a new item to the list
I have some react-router routes as follows:
<Route name='products' path='products' handler={ProductsContainer}>
<Route name='productNew' path='new' handler={ProductNew} />
<DefaultRoute handler={ProductsList} />
</Route>
so that either the list or the form are shown but not both.
What I'd like to do is to have the application re-route back to the list once a new item has been successfully added.
My solution so far is to have a .then() after the async dispatch:
dispatch(actions.addProduct(product)
.then(this.transitionTo('products'))
)
Is this the correct way to do this or should I fire another action somehow to trigger the route change?
If you don't want to use a more complete solution like Redux Router, you can use Redux History Transitions which lets you write code like this:
export function login() {
return {
type: LOGGED_IN,
payload: {
userId: 123
}
meta: {
transition: (state, action) => ({
path: `/logged-in/${action.payload.userId}`,
query: {
some: 'queryParam'
},
state: {
some: 'state'
}
})
}
};
}
This is similar to what you suggested but a tiny bit more sophisticated. It still uses the same history library under the hood so it's compatible with React Router.
I ended up creating a super simple middleware that roughtly looks like that:
import history from "../routes/history";
export default store => next => action => {
if ( ! action.redirect ) return next(action);
history.replaceState(null, action.redirect);
}
So from there you just need to make sure that your successful actions have a redirect property. Also note, this middleware does not trigger next(). This is on purpose as a route transition should be the end of the action chain.
For those that are using a middleware API layer to abstract their usage of something like isomorphic-fetch, and also happen to be using redux-thunk, you can simply chain off your dispatch Promise inside of your actions, like so:
import { push } from 'react-router-redux';
const USER_ID = // imported from JWT;
function fetchUser(primaryKey, opts) {
// this prepares object for the API middleware
}
// this can be called from your container
export function updateUser(payload, redirectUrl) {
var opts = {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
};
return (dispatch) => {
return dispatch(fetchUser(USER_ID, opts))
.then((action) => {
if (action.type === ActionTypes.USER_SUCCESS) {
dispatch(push(redirectUrl));
}
});
};
}
This reduces the need for adding libraries into your code as suggested here, and also nicely co-locates your actions with their redirects as done in redux-history-transitions.
Here is what my store looks like:
import { createStore, applyMiddleware } from 'redux';
import rootReducer from '../reducers';
import thunk from 'redux-thunk';
import api from '../middleware/api';
import { routerMiddleware } from 'react-router-redux';
export default function configureStore(initialState, browserHistory) {
const store = createStore(
rootReducer,
initialState,
applyMiddleware(thunk, api, routerMiddleware(browserHistory))
);
return store;
}
I know I am little late in the party as react-navigation is already included in the react-native documentation, but still it can be useful for the user who have used/using Navigator api in their apps.
what I tried is little hackish, I am saving navigator instance in object as soon as renderScene happens-
renderScene(route, navigator) {
const Component = Routes[route.Name]
api.updateNavigator(navigator); //will allow us to access navigator anywhere within the app
return <Component route={route} navigator={navigator} data={route.data}/>
}
my api file is something lke this
'use strict';
//this file includes all my global functions
import React, {Component} from 'react';
import {Linking, Alert, NetInfo, Platform} from 'react-native';
var api = {
navigator,
isAndroid(){
return (Platform.OS === 'android');
},
updateNavigator(_navigator){
if(_navigator)
this.navigator = _navigator;
},
}
module.exports = api;
now in your actions you can simply call
api.navigator.push({Name:'routeName',
data:WHATEVER_YOU_WANTED_TO_PASS)
you just need to import your api from the module.
If you're using react-redux and react-router, then I think this link provides a great solution.
Here's the steps I used:
Pass in a react-router history prop to your component, either by rendering your component inside a react-router <Route/> component or by creating a Higher Order Component using withRouter.
Next, create the route you want to redirect to (I called mine to).
Third, call your redux action with both history and to.
Finally, when you want to redirect (e.g., when your redux action resolves), call history.push(to).