How to wait until redux prepares necessary data - javascript

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).

Related

Router.push is returning my objects as undefined, the array length is correct but the value is "" in Next.js [duplicate]

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);

How can I display the result of a promise on a webpage in a react export

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}</>;
};

How to deal with asynchronous props and state

I can't quite wrap my head around this.
I'm having to pass data that's fetched asynchronously. Issue is, the props are asynchronous as well. Here's a simplified version of the component:
import React, { Component } from 'react'
import CSVLink from 'react-csv'
import generateData from './customApi/generateData
type Props = { job?: JobType | undefined }
type State = { csvData: string[][] }
class MyComponent extends Component<Props, State> {
state = {
csvData = [],
}
generateCsv = async (jobId: string) => {
const csvData = await generateData(jobId)
this.setState({ csvData })
}
async componentDidMount() {
const { job } = this.props
await this.generateCsv(job.id)
}
render() {
const { csvData } = this.state
return (
<CSVLink data={csvData}>
<p>Download csv</p>
</CSVLink>
)
}
}
export default connectFirestore(
(db, params) => ({ getJob(db, params.id) })
)
Basically my props are fetched from an API call to firestore, so it takes a while to load the job. Issue is, when I'm trying to pass the jobId inside the async componentDidMount(), it ends up passing undefined, because the job props are not loaded yet.
I don't link the whole passing state to async call business going on, but I can't think of any other way, how I would passing the csvData from the generateDate() async call only once the Promise is resolved.
I guess the easiest way to approach this would be, to perhaps somehow check if the props inside the componentDidMount() are already fetched?
What would be the correct way to approach this?
You are missing to implement the constructor where the props are set
constructor(props){
super(props);
this.state = {
csvData = [],
};
}
componentDidMount(){
//This will work
console.log(this.props.job);
};
If job property should by async, do it async: rather than passing a value which will change in future, you can pass a Promise which resolves with the id, than change your componentDidMount as follows:
componentDidMount() {
const id = await this.props.job;
this.generateCsv(id)
}
Hope this helps.
Maybe you should change the code inside your parent component, I imagine that you are making the API call there, as you are passing this data as props in this component you are showing to us.
I also imagine that you are making the API call with the fetch command, which can have a .then(()=>{}) method triggered when the API call finished loading, after that you can change the state of that component carrying the API fetched data and THEN render this child. Something I used recently for my project was to load from API, update state and conditionally render the child component, which will not know I made an API call because I am passing already loaded data. Normally while it is waiting I put something like this:
if(this.state.dataFetched == null)
return(<h1>Loading page...</h1>)
else return <childComponent loadedData = {this.state.dataFetched}/>
And then access that data as this.props.loadedData
Not sure if it's the optimal solution, but it works.
I've decided to use componentDidUpdate() life-cycle method, where I'm comparing whether the props have already update and once they did, I'm calling the asynchornous function for generating the csv data.
async componentDidUpdate(prevProps: Props) {
if (prevProps !== this.props && !!this.props) {
const { job } = this.props
if (job) {
await this.generateCsv(job.id)
}
}
}
This way we generate new data every time the data inside the props.job changed and also we don't attempt to call generateCsv() on undefined while it's still being fetched from firestore.

In Redux, where does the state actually get stored?

I searched a bit about this question but found very vague answers. In redux, we know that the state is stored as an object. But where is this state stored actually? Is it somehow saved as a file which can be accessed by us later on? What I know is that it does not store it in a cookie format or in the browser's local storage.
The state in Redux is stored in memory, in the Redux store.
This means that, if you refresh the page, that state gets wiped out.
You can imagine that store looking something like this:
function createStore(reducer, initialState) {
let state = initialState // <-- state is just stored in a variable that lives in memory
function getState() {
return state
}
function dispatch(action) {
state = reducer(state, action) // <-- state gets updated using the returned value from the reducer
return action
}
return {
getState,
dispatch
}
}
The state in redux is just a variable that persists in memory because it is referenced (via closure) by all redux functions.
Here's a simplified example of what is going on:
function example() {
let variableAvailableViaClosure = 0
function incrementTheClosureVariable() {
variableAvailableViaClosure += 1
}
function getTheClosureVariable() {
return variableAvailableViaClosure
}
return {
incrementTheClosureVariable,
getTheClosureVariable
}
}
let data = example()
// at this point example is finished
// but the functions it returned
// still have access to the (internal) variable via closure
console.log(
data.getTheClosureVariable() // 0
)
data.incrementTheClosureVariable()
console.log(
data.getTheClosureVariable() // 1
)
Furthermore, the statement
In redux, we know that the state is stored as an object.
isn't correct. State in redux can be any valid javascript value, not just an object. It just usually makes the most sense for it to be an object (or a special object like an array) because that allows for a more flexible data structure (but you could make the state just be a number for example, if you wanted to).
Check out the actual Redux implementation for more details.
If you want the state to persist in a cookie or localStorage, you would enhance the store such that, on top of updating the state in memory, it will save to your desired storage as well (and load from that storage when the store is initialized)
States are stored in redux-store. Redux Store is a global store which can be accessed anywhere/any components.
Let consider an example of getting Index of data using third party API. The following snippet uses componentWillMount which will trigger a fetch call using redux action.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchDataFromUrl } from '../actions/index.js';
class Indexdata extends Component {
constructor(props){
super(props);
this.state = {
text: ''
}
}
componentWillMount(){
let thisVal = this;
thisVal.props.fetchIndexofData()
}
componentWillReceiveProps(nextProps){
this.setstate({
text: nextProps.indexData.text
})
}
render(){
return(
<div>
<Navbar />
<h2 className="prescription-index-title">Index of Data</h2>
</div>
)
}
}
function mapStateToProps(state){
return{
indexData: state.fetchedData
}
}
function mapDisptachToProps(dispatch){
return {
fetchIndexofData: () => dispatch(fetchDataFromUrl(access_token))
};
};
export default connect(mapStateToProps, mapDisptachToProps)(IndexData);
The above snippet will fetch index of data using a redux action. The below code is a redux action,
export function fetchDataFromUrl(){
return(dispatch) => {
const base_url = "https://api_serving_url.com"
fetch(base_url, {
method: 'GET'
})
.then(response => response.json())
.then(data => {
dispatch({
type: "INDEX_DATA",
data: data
})
})
}
}
Redux action will dispatch data to reducer, where state will be initialized in redux store. The following code snippet is redux-reducer
export function fetchedData(state = [], action) {
switch(action.type) {
case "INDEX_DATA":
return action.data;
default:
return state;
}
}
State stored in redux store will be mapped using function mapStateToProps, implemented in the above component. Now you can access the state using props in the respective component. Lifecyclehook componentWillReceiveProps will be able to fetch the state stored redux store.
You can access the State by means of using store.getState() in any component.The only drawback of using reducer state, is that it will reset the state when you refresh the component/application. Go through Reducer Store , for more information.

Why would I use Redux Promise Middleware over Redux Promise?

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

Categories