Im about to setup a product Page with ionic-react and #ionic-react-router.
The products have different variants, soo I created a optional url parameter to enable smooth sharing.
My route is defined like this:
product: {
component: ProductPage,
background: '/images/dashboard-navigation-productOverview.png',
key: 'PRODUCT',
path: '/product/:id/:variantId?'
},
My ProductPage Component looks like this:
import React, { useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import { IonPage, IonContent } from '#ionic/react';
....
const ProductPage = ({match}) => {
const [variants, setVariants] = useState([]);
const [source, setSource] = useState(null);
const [variant, setVariant] = useState(null);
useEffect(() => {
(async () => {
const response = await productService.getProduct(parseInt(match.params.id, 10))
setSource(response);
})();
}, [match.params.id]);
useEffect(() => {
const variants = productHelpers.allVariantsFromSource(source);
if (match.params.variantId) {
const variant = productHelpers.getProductVariationById(variants, parseInt(match.params.variantId, 10));
setVariant(variant || variants[0]);
} else {
// 0 is always main detail
setVariant(variants[0]);
}
setVariants(variants);
}, [source, match.params.variantId]);
return <IonPage>
<Toolbar title={(source) ? source.name : ''}/>
<IonContent>
<Product source={source} variant={variant} variants={variants}/>
</IonContent>
</IonPage>
}
export default ProductPage;
And somewhere in product I want to change the variation with replacing the url.
So the user can use the back button, to get back where he comes from (mostly product list).
import React, {useContext} from "react";
import {IonButton, NavContext} from '#ionic/react';
const ProductVariant = ({source, newVariant}) => {
const {navigate} = useContext(NavContext);
return (
<IonButton
onClick={() => navigate(`/product/${source.id}/${newVariant.id}`, 'none', 'replace')}>{newVariant.name}</IonButton>
);
};
My Problem
The page URL is updating like I want to, The back function is working too.
But the page transition is still happening.
What i'm doing wrong??
Related
I'd like to be able to listen to query param change events, preferably via a hook, but anything would be nice. I can't find anything that suggests it's even possible with react-router, so other suggestions without it are welcome too.
There's nothing in react-router-dom#5 that directly does this, so you'd need to implement this yourself. You can use the useLocation hook to access the location.search value to create a URLSearchParams object, then a useEffect hook to issue the side-effect based on any specific queryString parameter updating.
Example:
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
...
const { search } = useLocation();
const searchParams = new URLSearchParams(search);
const param = searchParams.get("param");
useEffect(() => {
// issue side-effect
}, [param]);
For RRDv5 there is this recipe to abstract the access of the query params:
import { useMemo } from 'react';
import { useLocation } from 'react-router-dom';
const useQuery = () => {
const { search } = useLocation();
return useMemo(() => new URLSearchParams(search), [search]);
};
...
import { useEffect } from 'react';
import { useQuery } from '../path/to/hooks';
...
const searchParams = useQuery();
const param = searchParams.get("param");
useEffect(() => {
// issue side-effect
}, [param]);
You can use useQuery and the useEffect hook to create another custom hook.
import { useEffect } from 'react';
import { useQuery } from '.';
const useQueryParam = (paramKey, cb) => {
const searchParams = useQuery();
const param = searchParams.get(paramKey);
useEffect(() => {
if (param) {
cb(param);
}
}, [param]);
};
...
import { useQueryParam } from '../path/to/hooks';
...
useQueryParam(
"myParameter",
(paramValue) => {
// do something with "myParameter" param value
},
);
did you tried below
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
function MyComponent() {
const location = useLocation();
useEffect(() => {
console.log('Location changed');
}, [location]);
...
}
I am new to Shopify App development and I try to implement routing inside my embedded Shopify App.
I have setup the ClientRouter and also integrated it inside the app.js (see below). When I set Navigation Links through the partners Account, the navigation menu appears and the links and the redirecting work as well.
As soon as I try to navigate the user to a page on a Button click, I get the error:
Expected a valid shop query parameter
I am trying to navigate the user by just giving the path for the page:
<Button url="/users">Users</Button>
My other files are listed below:
index.js
import { Page, Heading, Button } from "#shopify/polaris";
const Index = () => (
<Page>
<Heading>Index PAGE</Heading>
<Button url="/users"> Users </Button>
</Page>
);
export default Index;
app.js
import ApolloClient from "apollo-boost";
import { ApolloProvider } from "react-apollo";
import App from "next/app";
import { AppProvider } from "#shopify/polaris";
import { Provider, useAppBridge } from "#shopify/app-bridge-react";
import { authenticatedFetch } from "#shopify/app-bridge-utils";
import { Redirect } from "#shopify/app-bridge/actions";
import "#shopify/polaris/dist/styles.css";
import translations from "#shopify/polaris/locales/en.json";
import ClientRouter from "../components/ClientRouter";
function userLoggedInFetch(app) {
const fetchFunction = authenticatedFetch(app);
return async (uri, options) => {
const response = await fetchFunction(uri, options);
if (
response.headers.get("X-Shopify-API-Request-Failure-Reauthorize") === "1"
) {
const authUrlHeader = response.headers.get(
"X-Shopify-API-Request-Failure-Reauthorize-Url"
);
const redirect = Redirect.create(app);
redirect.dispatch(Redirect.Action.APP, authUrlHeader || `/auth`);
return null;
}
return response;
};
}
function MyProvider(props) {
const app = useAppBridge();
const client = new ApolloClient({
fetch: userLoggedInFetch(app),
fetchOptions: {
credentials: "include",
},
});
const Component = props.Component;
return (
<ApolloProvider client={client}>
<Component {...props} />
</ApolloProvider>
);
}
class MyApp extends App {
render() {
const { Component, pageProps, host } = this.props;
return (
<AppProvider i18n={translations}>
<Provider
config={{
apiKey: API_KEY,
host: host,
forceRedirect: true,
}}
>
<ClientRouter />
<MyProvider Component={Component} {...pageProps} />
</Provider>
</AppProvider>
);
}
}
MyApp.getInitialProps = async ({ ctx }) => {
return {
host: ctx.query.host,
API_KEY: process.env.SHOPIFY_API_KEY,
};
};
export default MyApp;
ClientRouter.js
import { withRouter } from "next/router";
import { ClientRouter as AppBridgeClientRouter } from "#shopify/app-bridge-react";
function ClientRouter(props) {
const { router } = props;
return <AppBridgeClientRouter history={router} />;
}
export default withRouter(ClientRouter);
I am really looking forward to someone who can help me out! Thanks in advance!
i stuck in a project where i have to implement JSON Place Holder Post API and JSON Place Holder Comment API both API in a particular component.Actually my task is build a project like a facebook post component where user can post and comment. I implemented Post API successfully but i couldn't find any solution to use comment API. I did all thing but it's not show in my Home component.
How can i implement comment api in my home component
my console said it present but i couldn't show this
This is Home.js File
import React, { useEffect, useState } from 'react';
import Post from '../Post/Post';
import Comment from '../Comment/Comment';
import './Home.css';
const Home = () => {
const [post,setPost] = useState([]);
const [comment,setComment] = useState([]);
useEffect(()=>{
fetch('https://jsonplaceholder.typicode.com/posts')
.then(res=>res.json())
.then(data=>setPost(data))
},[])
useEffect(()=>{
fetch('https://jsonplaceholder.typicode.com/comments')
.then(res=>res.json())
.then(data=>setComment(data))
},[])
return (
<div>
<div>
{
post.map(post=><Post post={post}></Post>)
}
</div>
<div className="main-body">
{
comment.map(comment=><Comment comment={comment}></Comment>)
}
</div>
</div>
);
};
export default Home;
This comment.js File
import React from 'react';
const Comment = (props) => {
const {name,email} = props.comment.name;
console.log(props.comment);
return (
<div>
{name}
{email}
</div>
);
};
export default Comment;
This is post.js File
import React from 'react';
import './Post.css';
const Post = (props) => {
const {title,body} = props.post;
return (
<div className="body-style">
<h1 className="name">{title}</h1>
<p>{body}</p>
</div>
);
};
export default Post;
Please help me I need solution
The structure is incorrect, in order to do that, comment should be children of post, and home will pass data to the post. Since you fetch data from 2 difference API, you need to combined it into 1 source and pass that down.
Home.js
import React, { useEffect, useState } from 'react';
import Post from '../Post/Post';
import './Home.css';
const Home = () => {
const [post,setPost] = useState([]);
const [comment,setComment] = useState([]);
const [ info, setInfo ] = useState([]);
useEffect(()=>{
fetch('https://jsonplaceholder.typicode.com/posts')
.then(res=>res.json())
.then(data=>setPost(data))
},[])
useEffect(()=>{
fetch('https://jsonplaceholder.typicode.com/comments')
.then(res=>res.json())
.then(data=>setComment(data))
},[])
//Function to combine post and comment base on ID
const merge = (post, comment) => {
const temp = [];
post.forEach((x) => {
comment.forEach((y) => {
if (x.id === y.id) {
let cName = y.name;
let cEmail = y.email;
let cBody = y.body;
temp.push({ ...x, cName, cEmail, cBody });
}
});
});
return temp;
};
useEffect(
() => {
setInfo(merge(post, comment));
console.log(info);
},
[ post, comment ]
);
return (
<div>
{info.map((each) => <Post key={each.id} data={each} />)}
</div>
);
};
export default Home;
Post.js
import React from 'react';
import Comment from './Comment';
const Post = (props) => {
const { title, body, cEmail, cName } = props.data;
return (
<div className="body-style">
<h1 className="name">{title}</h1>
<p>{body}</p>
<Comment email={cEmail} name={cName} />
</div>
);
};
export default Post;
Comment.js
import React from 'react';
const Comment = ({ name, email }) => {
return (
<div>
{name}
{email}
</div>
);
};
export default Comment;
I need global app-wide access to a VideoElement to play it on user events on browsers like Safari and was wondering if storing the VideoElement in a context would be the best way to do that. I programmatically play my video through a redux action and in Safari that is not possible unless it has been played once through a user triggered event (like a click)
Is it possible to store an element (ref) within a context? The VideoElement will be then rendered within the component which I want to have my video, and then other components will also have access to the context and be able to call functions such as usePlayVideo that based on the context's state, will either call videoElement.play() if this is the first time the video is being played, or dispatch the redux action to play the video programmatically otherwise
It is possible to store a ref into context! You need to create a context at first. Then you need to pass value to the context provider and create a ref object using useRef hook. After that, you pass the ref into the value.
Now, You have a ref object sharing between components under the context provider and if you want to retrieve or pass a new ref, you could use useContext hook to deal with it.
Here is the demo (codesandbox).
Here is the sample code.
import { createContext, useContext, useEffect, useRef, useState } from "react";
import "./styles.css";
const MyContext = createContext();
export const ContextStore = (props) => {
const ref = useRef();
return <MyContext.Provider value={ref}>{props.children}</MyContext.Provider>;
};
export default function App() {
return (
<>
<ContextStore>
<MyComponent />
<MyComponent2 />
</ContextStore>
</>
);
}
const MyComponent = () => {
const myContext = useContext(MyContext);
return (
<div className="App" ref={myContext}>
<h1>Hello MyComponent1</h1>
</div>
);
};
const MyComponent2 = () => {
const myContext = useContext(MyContext);
const [divRef, setDivRef] = useState();
useEffect(() => {
setDivRef(myContext);
}, [myContext]);
return (
<div className="App">
<h1>{divRef?.current && divRef.current.innerText}</h1>
</div>
);
};
You can use this approach:
VideoContext.js
import { createContext, createRef, useContext } from "react";
const VideoContext = createContext();
const videoRef = createRef();
export const VideoContextProvider = (props) => {
return (
<VideoContext.Provider value={videoRef}>
{props.children}
</VideoContext.Provider>
);
};
export const useVideoContext = () => useContext(VideoContext);
and App.js for example:
import { useState, useEffect } from "react";
import { useVideoContext, VideoContextProvider } from "./VideoContext";
const SomeComponent = () => {
const videoRef = useVideoContext();
return (
<div ref={videoRef}>
<h1>Hey</h1>
</div>
);
};
const SomeOtherComponent = () => {
const [ref, setRef] = useState();
const videoRef = useVideoContext();
useEffect(() => {
setRef(videoRef);
}, [videoRef]);
return (
<div>
<h1>{ref?.current?.innerText}</h1>
</div>
);
};
export default function App() {
return (
<>
<VideoContextProvider>
<SomeComponent />
</VideoContextProvider>
{/* ... */}
{/* Some other component in another part of the tree */}
<VideoContextProvider>
<SomeOtherComponent />
</VideoContextProvider>
</>
);
}
code sandbox
Why not? I'll say. Let's see if we can setup an example.
const fns = {}
const addDispatch = (name, fn) => { fns[name] = fn }
const dispatch = (name) => { fns[name] && fns[name]() }
const RefContext = createContext({ addDispatch, dispatch })
export default RefContext
const Child1 = () => {
const [video, dispatchVideo] = useState(...)
const { addDispatch } = useContext(RefContext)
useEffect(() => {
addDispatch('video', dispatchVideo)
}, [])
}
const Child2 = () => {
const { dispatch } = useContext(RefContext)
const onClick = () => { dispatch('video') }
...
}
The above two childs do not have to share the same ancestor.
I didn't use ref the way you wanted, but i think you can pass your ref to one of the function. This is a very basic idea. I haven't tested it yet. But seems it could work. A bit
I used this approach:
first I creacted the context and ContextProvider;
import React, { useRef } from "react";
export const ScrollContext = React.createContext();
const ScrollContextProvider = (props) => {
return (
<ScrollContext.Provider
value={{
productsRef: useRef(),
}}
>
{props.children}
</ScrollContext.Provider>
);
};
export default ScrollContextProvider;
then Added my provider in my index.js:
root.render(
<React.StrictMode>
<ScrollContextProvider>
<App />
</ScrollContextProvider>
</React.StrictMode>
);
after that I used my context where I needed it:
import React, { useContext } from "react";
import { ScrollContext } from "../../store/scroll-context";
const Products = () => {
const scrollCtx = useContext(ScrollContext);
return (
<section ref={scrollCtx.productsRef}>
// your code...
</section>
);
};
In my case I wanted to to scroll to the above component clicking a button from a different component:
import React, { useContext } from "react";
import { ScrollContext } from "../../store/scroll-context";
function Header() {
const scrollCtx = useContext(ScrollContext);
const scrollTo = () => {
setTimeout(() => {
scrollCtx.productsRef.current.scrollIntoView({ behavior: "smooth" });
}, 0);
};
return (
<header>
//your code ...
<button alt="A table with chair" onClick={scrollTo}>Order Now<button />
</header>
);
}
No. It's not possible to use Ref on context api. React ref is considered to be used on rendering element.
What you're looking for is to forward the ref, so that you can consume them wherever you want.
I am using axios library to fetch data from a json file through json-server.
When I am loading and using the response object in a single component it is working perfectly. But when I am passing this response object to child component from parent component it is not loading the data. Also not receiving any errors, can someone please help me to understand the difference and what is wrong with my approach?
//Scenario-1 : working perfectly fine:
import React, { useState, useEffect } from 'react';
import Display from './Display';
import Note from './note'
import axios from 'axios';
const App = () => {
const [notes, setNotes] = useState([])
const hook = () => {
axios.get('http://localhost:3001/notes')
.then(response => {
setNotes(response.data)
})
}
useEffect(hook, [])
return (
<div>
{notes.map(n => <Note key={n.id} note={n} />)}
</div>
)
}
export default App;
//Scenario-2 : Not working as expected, also no errors.
const Display = (props) => {
//Receiving data here, can see the values in console.
console.log('inside display, props.notex: ', props.notex);
const [notes, setNotes] = useState(props.notex);
//Blank object, why useState() method is not setting the value of "notes" from "props.notex".
console.log('inside display, notes: ', notes);
const generateRows = () => {
console.log('generateRows: ', notes)
return (
notes.map(n => <Note key={n.id} note={n} />)
)
}
return (
<div>
<ul>
{generateRows()}
</ul>
</div>
)
}
const App = () => {
const [notes, setNotes] = useState([])
const hook = () => {
axios.get('http://localhost:3001/notes')
.then(response => {
setNotes(response.data)
})
}
useEffect(hook, [])
return (
<div>
<Display notex={notes} />
</div>
)
}
export default App;
My guess is that useState is asynchronous, same as setState in Class components. Due to its async nature, you are not able to log anything - the log gets executed before the useState actually does anything.
If you really want to do it this way, you could initialize the value of the useState as an empty array and set up a useEffect hook, with the props.notex in your dependency array, something like this:
useEffect(() => {
if (props.notex) setNotes(props.notex)
}, [props.notex])
And then in the return
return (
<div>
<ul>
{notes.length && generateRows()}
</ul>
</div>
)
But you could just pass the props down from the parent to child without setting the state in the child component.
Hope this helps!