I'm using onsen-ui to style a meteor app with React for the frontend. As I understand it, onsen-ui manages navigation by pushing pages to a stack, where each page has a unique identifier.
Here is how my pages are loaded in App.js
loadPage = (page) => {
const currentPage = this.navigator.pages.slice(-1)[0]
if(currentPage.key != page.name){
this.navigator.pushPage(
{
component: page,
props: { key: page.name }
},
{
animation: 'fade',
animationOptions: {duration: 0.3, timing: 'ease-in'}
},
);
}
}
So far everything works fine. But I have now included redux in my project, so I have some components which are connected to the store by react-redux's connect() function.
The problem is that for every component that connect wraps, the name property becomes Connect, so onsen-ui is having to deal with multiple pages with the same name in its stack.
As an example, let's say I have a component defined below
const ComponentName = props => {
return (
<p>Test component</p>
)
}
export default connect()(ComponentName)
ordinarily, ComponentName.name returns ComponentName but once its wrapped with connect, ComponentName.name returns Connect
Is it at all possible to modify the name value for the wrapped component?
Every suggestion is welcome.
Edit: Following Vlatko's lead, this is how I eventually solved the problem.
export const getPageKey = (page) => {
// returns a page key
let key;
if (page.name === 'Connect') {
key = page.displayName
// key = page.WrappedComponent.name
// react-redux connect returns a name Connect
// .displayName returns a name of form Connect(ComponentName)
return key.replace('(', '').replace(')', '')
}
else {
key = page.name
return key
}
}
So for every component I just get the key with getPageKey(ComponentName)
Edit 2. This approach doesn't work in production.
In production mode, I get single letters for page.displayName and those letters are hardly unique, which means I'm back where I started.
I need to find another approach. I'll update whatever I find.
This is happening because you are exporting your component through a higher order function (Connect), which is basically a closure in JavaScript.
The name of the HOC that you are using is Connect, and this is returned.
However, it is possible to get the name of the component passed into the connect HOC.
ComponentName.name // Connect
ComponentName.displayName // Connect(ComponentName)
ComponentName.WrappedComponent.name // ComponentName
I’m not familiar with onsen-ui, and it looks like Vlatko has you covered, but couldn’t you also give the new connected component a name? For example:
const ComponentName = props => {
return (
<p>Test component</p>
)
}
const ConnectedComponentName = connect()(ComponentName)
export default ConnectedComponentName;
Then hopefully you would be able to access the unique ConnectedComponentName
Related
I am trying to send an id to the next page when the user navigates.
I have a homepage where I am fetching an Array of data and using .map to display it in a kind of card-based UI.
Now, when the user clicks the card, they will be navigated to the next page display details about that card.
Suppose the home page is here - localhost:3000
And user clicks the card with an id of 234
They will be navigated to the next page as :
The next page is as - localhost:3000/user/234
Now here I want to display information about that card with an id of 234. FOr that I do need to make a fetch request as such fetch(https://userdatabase/234)
The above 234 is a dynamic id for sure, How can I let this fetch request know to change this id every time a new card has been clicked? Or in other words, How this page "knows" the id of the card ?
Right now, I', using a workaround as :
When the user is at the second page, the URL will be like this localhost:3000/user/386
Get this id in NextJS using useRouter as :
import {useRouter} from 'next/router'
`const router = useRouter()`
fetch(`localhost:3000/user/${router?.query?.user})
I understand taking id from URL and making a fresh quest is not ideal at all and this is causing stale caching issue on the second page.
How do I resolve this in a better way?
Thanks so much for reading.
you need to make a dynamic route : Next.js Docs
for your case make a file pages/user/[id].js
1. Client side
access id with this snippet :
import { useRouter } from 'next/router'
const Component = () => {
const router = useRouter()
const { id } = router.query
return <p>{id}</p>
}
export default Component
2. Server side
you can use it any of the data fetching functions
Snippet for SSR :
export async function getServerSideProps({ params }) {
const { id } = params
// fetch data from database with id
return {
props: {}, // will be passed to the page component as props
}
}
More on what gets passed to data fetching functions as Context : Context Parameter
Add id to the dependencies array of useEffect().
Something along these lines:
import { useState, useEffect } from "react";
import { useRouter } from 'next/router';
function Page() {
const router = useRouter();
const [page, changePage] = useState();
// `query` can only be fully parsed client-side
// so `isReady` flag is needed
const { query, isReady } = router;
const { id } = query;
// also need to parse the query value to undefined or string
const parsedID = id === undefined
? undefined
: Array.isArray(id)
? id[0]
: id;
useEffect(() => {
// refuse to run the effect if query is not ready
// or ID is undefined
if (!isReady || !parsedID ) {
return;
}
// this looks ugly
// but pure promise syntax is even uglier
// and `useEffect()` doesn't accept async functions
(async () => {
// errors are assumed to be handled in the `_app` component
// so no error-handling logic here
const response = await fetch(`localhost:3000/user/${parsedID}`);
const newPage = await response.json();
changePage(newPage);
})()
}, [isReady, parsedID]);
return (
<>
{!page
// show loading placeholder until the page is fetched
? <div>Loading...</div>
// pass the `page` value to whatever component you need
: ...
}
</>
)
}
export default Page;
In React js, What I want to do is: declare an array of objects (routes) and export.
For conditional declaration of array of objects, I want to use an state variable (which returns whether user logged in or not).
Aim:
user logged in, show Put Rating option
user not logged in, dont show Put Rating option.
The file of array of objects which I have to export for mapping
import {AppState} from '../AppContext';
function IsLoggedIn(){
const {user} = AppState();
return user===null;
}
const routes=[
{
title:"home page",
show:true,
},
{
title:"put rating",
show: IsLoggedIn(),
}
];
export default routes;
It returns error like hooks can only be used inside body of functions.
Move your AppState to a context/reducer so that whenever a user authenticates we can properly react to this state change then
const routes=[
{
title:"home page",
private:false,
},
{
title:"put rating",
private:true,
}
];
Create a separate component called PrivateWrapper or anything you like and inside that put this logic
export default function PrivateWrapper(props){
const {user} = AppState();
return !user ? null : props.children;
}
Finally when looping through the routes array just check if private is true then wrap the element with PrivateWrapper
routes.map(route =>(
route.private ?
<PrivateWrapper>{route.title}</PrivateWrapper> : route.title
))
You can also use this wrapper with any element you want.
This question already has answers here:
useRouter/withRouter receive undefined on query in first render
(9 answers)
Closed 8 months ago.
I am using a dynamic router in Next.js, and this is the code
import { useRouter } from "next/router";
const exampleFunction = () => {
const router = useRouter();
const {slug} = router.query;
console.log(slug);
return (
<h1>{slug}</h1>
);
};
export default exampleFunction;
The location of the file is:
lms/client/pages/teacher/course/test/[slug].js
When I access the link like this:
http://localhost:3000/teacher/course/test/testmw
I can see the slug is being rendered in the browser; however when I look at the console, I see undefined value before the actual value of the slug.
I wonder this is something I've done wrong or this is the expected behavior of Next.js?
I've been suggested a similar question. However, the answer is mostly about getting the route in server-side, I wonder if I can effectively do the same thing on the client-side.
"query: Object - The query string parsed to an object. It will be an empty object during prerendering if the page doesn't have data fetching requirements. Defaults to {}"
Initially, "query" object will be {} so "slug" will be undefined. This is what you should do :
return (
{slug && <h1>slug</h1>}
);
If "slug" is not defined, do not render element.
To solve this problem, you need to get the "slug" on the server with "getServerSideProps" and pass it to the component:
export const getServerSideProps = async (context) => {
const { slug } = context.query;
// If slug is "undefined", since "undefined" cannot be serialized, server will throw error
// But null can be serializable
if (!slug) {
slug = null;
}
// now we are passing the slug to the component
return { props: { slug:slug } };
};
Now your component has props.slug.
const exampleFunction = (props) => {
const {slug}=props // you could just pass {slug} to the component
return (
{slug && <h1>slug</h1>}
);
};
export default exampleFunction;
It first shows undefined because it is impossible to get query from route at first render.
I assume you will get the actual value of slug after second render. You are doing alright , it is next.js behavior.
You can look at getStaticpaths or you can useEffect to check if query is loaded or not and perform your task as needed
How to export a variable or a list which is modified inside a function inside the class to other .js file?
renderBoxContent = () => {
let total = 0;
let Total = 0;
let itemList = [];
let data = [];
this.state.availableItems.map((items)=> {
if(items.purchase > 0){
Total += items.price*items.purchase;
total = items.price*items.purchase;
console.log(items.purchase);
console.log(total);
data.push(
{
key: items.name,
name: items.name,
// src: items.image,
singlePrice: items.price.toFixed(2),
purchase: items.purchase,
totalItemPrice: total.toFixed(2),
}
)
}
})
//how to export 'data' to other .js file?
itemList.push( <Table defaultExpandAllRows={false} locale={{emptyText: 'Empty Cart'}} style={{background: '#ffffff'}} pagination={false} columns={column} dataSource={data} size="small" />)
itemList.push( <h3 style={{paddingLeft:15,paddingTop:20}}> Total {Total.toFixed(2)}</h3>)
return itemList;
}
Should be able to access and import the 'data' list variable in other .js file
What about using React Context?
First create and export your context
export const DataContext = React.createContext();
Wrap your components in a provider component and give it your data
<DataContext.Provider value={data}>
And consume it like this in your other component
import DataContext from '...' // add your path
const data = useContext(DataContext)
If your components are either parents or grand parents of each other, i recommend you pass the value as props between components instead.
it seems like you are working with react, so simply put you should look into Redux or context API, instead of creating custom functions to import and export data from one file to an other.
both of above are used to create a global state which can be accessed in any file of your project.
it will be little bit difficult to understand them at first, but once you get to know how to use them (one of them) it will make global state management very easy for you.
I have set up a React Frontend with a Node backend for an app I am trying to make. I have successfully created a server which is hosting my data, which I can then access and receive into my React Frontend. I am able to console.log the data I want and successfully saved it to the state (I think?). My issue is that I can't seem to actually pass the information contained in State into the child component.
Units.js
import UnitsCard from "./InfoCardUnits";
import React, { Component } from "react";
const axios = require("axios");
class Units extends Component {
constructor(props) {
super(props);
this.state = {
units: []
};
}
fetchData() {
axios
.get("http://localhost:3001/allData/units")
.then(response => {
// handle success
// console.log("Success");
this.setState({ units: response.data });
})
.catch(error => {
// handle error
console.error(error);
});
}
componentDidMount() {
this.fetchData();
}
render() {
// this console.log will show the data I want to send as props into my child component.
console.log(this.state.units[0]);
return <UnitsCard props={this.state.units[0]} />;
}
}
export default Units;
InfoUnitCard.js
import "../index.css";
function UnitsCard(props) {
// this console.log will show the "props" information that I want to use in my unit card. But the information itself won't actually show in the browser.
console.log(props);
return (
<div className="card">
<h2>{props.name}</h2>
<h2>{props.category}</h2>
<h2>{props.inf_melee}</h2>
</div>
);
}
export default UnitsCard;
When I console.log the state in either of the components it successfully shows the information I am trying to send. But I can't actually get that information to render. Any help or insights would be much appreciated.
EDIT: This has been resolved, thanks very much to everyone who chipped in an answer.
Avoid passing props in via the props keyword. Instead, consider making the following changes to your code:
render() {
// Get unit or empty object (makes code more readable in next step)
const unit = this.state.units[0] || {};
// Pass each piece of unit data in as a separate prop
return <UnitsCard
name={unit.name}
category={unit.category}
inf_melee={unit.inf_melee} />;
}
Alternatively, you could use the "spread" syntax available with ES6 to make this a little more concise:
render() {
// Get unit or empty object (makes code more readable in next step)
const unit = this.state.units[0] || {};
// Use spread operator to simplify passing of props to UnitsCard
return <UnitsCard {...unit} />;
}
Every thing you pass in the child component will be available in props object in the child component. In your case you are passing a 'props' to props object. This should be available as this.props.props.keyname. try changing your child component as follow.
function UnitsCard(props) {
// this console.log will show the "props" information that I want to use in my unit card. But the information itself won't actually show in the browser.
console.log(props);
return (
<div className="card">
<h2>{props.props.name}</h2>
<h2>{props.props.category}</h2>
<h2>{props.props.inf_melee}</h2>
</div>
);
}
You named your props props, so you can access to it with below code:
console.log(props.props);
you can pass like with a different name:
<UnitsCard child={this.state.units[0]} />
Then access to props with props.child, so your code will change to:
<h2>{props.child.name}</h2>