I was going through official Redux tutorial (Redux Essential) and I believe there is one thing missing. There is a simple "List/Details" example and it's all clear, but the Details page (SinglePostPage) will not work, if user types the exact address in the address bar manually. In other words, if you open the website directly on the SinglePostPage - the list of posts will not be fetched, thus Page will display: Post not found!. The only way to view the post, is to open the list first, then click "View Post".
Here is a SinglePostPage
import React from 'react'
import { useSelector } from 'react-redux'
export const SinglePostPage = ({ match }) => {
const { postId } = match.params
const post = useSelector(state =>
state.posts.find(post => post.id === postId)
)
if (!post) {
return (
<section>
<h2>Post not found!</h2>
</section>
)
}
return (
<section>
<article className="post">
<h2>{post.title}</h2>
<p className="post-content">{post.content}</p>
</article>
</section>
)
}
I could simply fetch this post, like it's done for posts, but my biggest concern is, that in the first render useSelector will return undefined post, before it will dispatch fetch action. How will I know if "undefined" means that post was not found or if it wasn't even fetched?
So my question is, what's the best way to handle it? Should I create a separate slice for single post with loading status and error? Similar to postsSlice? I'm not sure If I should have two slices per one feature. Should I?
EDIT: Just to clarify, I'm not saying that tutorial has a bug. It's a very simple, not-real-life-app sample. The fact that you can't open post's details by typing direct URL in the browser was not covered by the tutorial. So my question is about possible solution to this "issue".
Related
I am trying to get icon names from a weather API, and have my react display the icons.
The first part is working, the icon names do appear in the console log. The second part does not...
import { useSelector } from "react-redux";
export default function Icon() {
const icons = useSelector(
(state) =>
state.api.map((i) => {
console.log(i);
i.city.list.map((ii) => {
console.log(ii);
ii.weather.map((iii) => {
console.log("icon", iii.icon);
return iii.icon;
});
});
})
//state.api[0].city.list[0].weather[0].icon
);
console.log(icons);
return (
<div>
<img src={`https://openweathermap.org/img/wn/${icons}.png`} />
</div>
);
}
Any ideas?
From what you've provided so far it seems to work like so:
Make an API call to api.openweathermap.org/data/2.5/forecast?lat={lat}&lon={lon}&appid={API key} and it gives you the response. From that response icon data can be reached for example like this:
const obj = api response as javascript object...
const icon = obj.list[0].weather[0].icon;
and used like you've shown:
<img src={`https://openweathermap.org/img/wn/${icon}.png`} alt="icon" />
However, we can't see what your redux state model is and how requests are eventually saved to state and so on. Which is why it is not obvious what
//state.api[0].city.list[0].weather[0].icon
does as we don't know anything about state.api[0]. This is why it is essential to provide enough information to help you solve the problem at hand. You could've just posted us the openweather data api response example or even better, redux state from that useSelector without any mappings.
People are hesitant to help when they need to get credentials for an API when you could've just provided the information to begin with.
Not trying to be rude here, but make it as easy as possible to help you and things get resolved very quickly :) this is certainly not a tough one to solve when you have all the info needed.
You could show us the state completely and we'll solve it from there or probably come to the conclusion that there is some problem with the first part, meaning state.api[0] or state.api[0].city We can't know how you save the state, but in API you provided city for example doesn't have icon, but that list owns icons. But the tricky part here is that this is all quessing without the actual state.
Please provide more info
Without knowing an example of the icons variable, there is not much help that we can provide.
For now however, I will infer from the code that icons holds an array or some iterable content.
In my experience with java, it will try to apply the array object to the image src which will of course not work.
Generic example of what may work
What you should do is map the icons similar to this:
import { useSelector } from "react-redux";
export default function Icon() {
const icons = useSelector(
(state) =>
state.api.map((i) => {
console.log(i);
i.city.list.map((ii) => {
console.log(ii);
ii.weather.map((iii) => {
console.log("icon", iii.icon);
return iii.icon;
});
});
})
//state.api[0].city.list[0].weather[0].icon
);
console.log(icons);
return (
<div>
{icons.map((e,i)=>{
return <img src={`https://openweathermap.org/img/wn/${e}.png`} key={"iconsImg"+i}/>
})}
</div>
);
}
I am creating a custom blog listing page in React for a HubSpot site (I've already searched there, couldn't find anything relevant to my issue). I'm still somewhat new to React but it is my first project from scratch in over a year. Basically what I am trying to do is combine three separate blogs into one page and add filtering options to show only posts from certain blogs and/or posts with certain tags attached to them. Currently I am successfully pulling the post data and sorting it. I am almost finished with the filtering functionality, but I am hitting a snag when trying to reset all filters.
I understand that setState is asynchronous and you can't expect the state value to updated immediately. My issue is that even after I have successfully updated the state, the useEffect hook doesn't fire consistently. I have tabs at the top of the page to switch between blogs and that works perfectly, as soon as I click one it filters out every other blog post and I can go back and forth between them easily. The problem is on the reset, I have to click the button twice for it to work. I'm completely at a loss as to why this is. In the console I see that the useEffect function for blogActive fires after the first time I click the reset button, but the blog view doesn't change. The second time I click the reset button, the blog view changes but the useEffect function doesn't fire.
I know that useEffect isn't supposed to fire if the state isn't actually updated (its being set to undefined when the button is clicked, so the second time it won't change) but I am still confused as to why this happens with the reset button and not with the blog switch function. I was originally resetting the blog from the reset function instead of just the sort function but that wasn't working either and calling it from the sort function is simpler in terms of displaying results with resetting the other filters (I have removed the tag filtering functions and components and still get the same results). I've also tried calling the handleBlogSwitch method instead of using the setState hook but I get the same results.
I've pasted the most relevant code below, but I've also included a sandbox link with a skeleton of the whole project including the code to fetch the post data.
Any help would be much appreciated!
function Blog({ blogName, blogID, handleBlog }) {
return (
<div className="blog">
<button className={"blog-" + blogName} onClick={() => handleBlog(blogID)}>
{blogName}
</button>
</div>
);
}
export default Blog;
function Archive() {
const [page, setPage] = useState(1);
const [blogActive, setBlog] = useState();
const [resetCheck, setChecked] = useState([]);
const [postState, setPosts] = useState(postArray);
let postArray = [];
function handleBlogSwitch(id) {
setBlog(id);
}
function sortPosts(sortme) {
let resetSort = false;
if (sortme) {
sortme.sort((a, b) => b["dataset"]["date"] - a["dataset"]["date"]);
postArray = [...postArray, ...sortme];
} else if (sortme === false) {
resetSort = true;
setBlog(() => {
return undefined;
});
}
let filtered = postArray;
if(!resetSort) {
if (blogActive) {
filtered = blogFilter(filtered);
setLoader("spinner-loaded");
}
}
setPosts(filtered);
setLoader("spinner-loaded");
}
function resetFilters(resetThis) {
setChecked([]); // Uncheck everything
sortPosts(resetThis);
}
// Handle logic for blog and tag filters
useEffect(() => {
setBtnLoad("btn-load-more"); // add this to the useEffect hook for tags too.
if(blogActive) {// Don't run this on page load.
sortPosts();
console.log("test blogactive effect");
}
}, [blogActive]);
return (
<div className="results__container">
<div className="tabs__container">
{Blogs.blogs.map((blog, key) => (
<Blog
key={key}
blogName={blog.name}
blogID={blog.id}
handleBlog={handleBlogSwitch}
></Blog>
))}
</div>
<div className="filter__container">
<button onClick={() => resetFilters(false)}>Reset Filters</button>
<div className="posts__container">
{postState.slice(0, page * resultsPerPage).map((html, key) => (
<Posts key={key} postHtml={html.outerHTML}></Posts>
))}
<img className={loader} src={logoSrc} />
<button className={btnClass} onClick={() => setPage(page + 1)}>
Load More
</button>
</div>
</div>
</div>
);
}
export default Archive;
https://codesandbox.io/s/wonderful-lalande-0w16yu?file=/src/App.js
Update: I didn't really fix this bug I'm facing, but I found a workaround. Instead of relying on the value of blogActive in sortPosts(), I created a new boolean in sortPosts which is set to true when it is called from the reset function. This way I know that the states should be resetting and skip that code, even if they aren't caught up yet.
I am working on a web app similar to google docs; each page has three documents tabs which are dynamically rendered. Think of three documents in a single page that you can access via clicking different tabs.
const Draft = () => {
const [draftData, setDraftData] = useState<Document>(defaultDraftData);
const [activePage, setActivePage] = useState<
'One' | 'Two' | 'Three'
>('One');
const contents = {
One: {
Content: ContentOne,
props: {},
},
Two: {
Content: ContentTwo,
props: {},
},
Three: {
Content: ContentThree,
props: {},
},
};
const { Content, props } = contents[activePage];
useEffect(() => {
axios.get(apiRoute)
.then(function (response) {
setDraftData(response.data);
});
}
}, []);
return (
<div className="bg-offwhite flex flex-col">
<SideBar activePage={activePage} setActivePage={setActivePage} />
<LayoutApp>
<Header header={title} draftId={draftId} />
<Content {...props} />
</LayoutApp>
<UtilBar />
</div>
);
};
So in short:
this page loads draftData which includes content for all three document tabs
in each ContentOne, ContentTwo, etc: uses react-save to auto-save any changes and uses useState to keep track of its contents
component is responsible for changing the tab
And I am having the mismatch problem of client-side data and server-side data. Making changes on one tab will change the useState then make a PATCH request so changes are reflected to the server-side data.
However, changing to another tab and going back will not render the changes as it was stored in useState, while changes can be seen when the entire page reloads.
The easiest solution is to make a separate FETCH call every time user changes the document tab, but I think there’s a better way to do this. I would appreciate any thoughts you guys have!
While I do see some ways you can play with useState and even useContext to handle the states of the tabs, I believe making a fetch for every tab change is the cleaner solution, because it seems more logical.
You have the useEffect already, so you can simply add activePage to the dependencies array.
Another solution (perhaps a next step from what I mentioned above) is to use React Query, if it's possible to use external libraries. I'm sorry I can't dive deep on how you would implement that, so I'm just throwing this idea here and hope it helps!
I'm trying to make a Twitter component that loads the tweet from the twitter API and displays its HTML, which will be a <blockquote> and a <script> tag.
The advantage of this, if server side rendered, is that it would work even if the user has privacy settings that block the call to the twitter script: the user will still get the blockquote, which is better than the current behavior (shows nothing).
So, my idea was to do something like this:
import fetch from 'node-fetch'
function Tweet(props) {
return <>
<div dangerouslySetInnerHTML={{
__html: props.__html
}} />
<p>here I am</p>
</>
}
Tweet.getInitialProps = async ctx => {
console.log("here I am on the road again")
const res = await fetch(`https://api.twitter.com/1/statuses/oembed.json?id=${ctx.tweetId}`)
const json = await res.json()
return { __html: json.html }
}
export default Tweet
And then on the pages use it like <Tweet tweetId="blah" />.
I found two problems with my idea though:
As per docs I cant access the tweetId property on getInitialProps
getInitialProps is never called. The <p>here I am</p> appears on the HTML and the log is never printed anywhere.
So, my question is: what I am doing wrong? Is this even possible to do?
Thanks!
As per Next.js documentation, getInitialProps can only be used to a page, and not in a component:
getInitialProps can only be added to the default component exported by a page, adding it to any other component won't work.
I am using react-router for client side routing. I have a button and when some one clicks the button, I want to redirect the user to a different url.
For e.g I want to redirect the user to "http://www.google.com". I used navigation mixin and used this.transitionTo("https://www.google.com"). But when I do this I get this error
Invariant Violation: Cannot find a route named "https://www.google.com".
I can use window.location but is that the right way to go?
As pointed out in the comments to this answer, default way of solving this would be to use anchor element (the a tag) with href attribute that points at the destination URL that you'd like to route the user to. A button that has appearance of a button but behavior or an anchor is pretty much a web anti-pattern. See more info in this answer: https://stackoverflow.com/a/1667512/1460905.
That said, there certainly is a potential scenario when a web app needs to perform some action and only then redirect the user. In this case, if primary action the user takes is submitting some data or really performing an action, and redirect is more of a side-effect, then the original question is valid.
In this case, why not use location property of window object? It even provides a nice functional method to go to external location. See the ref.
So, if you have a component, say
class Button extends Component {
render() {
return (
<button onClick={this.handleClick.bind(this)} />
);
}
}
then add handleClick that would make the component look like
class Button extends Component {
handleClick() {
// do something meaningful, Promises, if/else, whatever, and then
window.location.assign('http://github.com');
}
render() {
return (
<button onClick={this.handleClick.bind(this)} />
);
}
}
No need to import window since it's global. Should work perfectly in any modern browser.
Also, if you have a component that is declared as a function, you may possibly use the effect hook to change location when state changes, like
const Button = () => {
const [clicked, setClicked] = useState(false);
useEffect(() => {
if (clicked) {
// do something meaningful, Promises, if/else, whatever, and then
window.location.assign('http://github.com');
}
});
return (
<button onClick={() => setClicked(true)}></button>
);
};
You don't need react-router for external links, you can use regular link elements (i.e. <a href="..."/>) just fine.
You only need react-router when you have internal navigation (i.e. from component to component) for which the browser's URL bar should make it look like your app is actually switching "real" URLs.
Edit because people seem to think you can't use an <a href="..." if you need to "do work first", an example of doing exactly that:
render() {
return <a href={settings.externalLocation} onClick={evt => this.leave(evt)}/>
}
async leave(evt) {
if (this.state.finalized) return;
evt.preventDefault();
// Do whatever you need to do, but do it quickly, meaning that if you need to do
// various things, do them all in parallel instead of running them one by one:
await Promise.all([
utils.doAllTheMetrics(),
user.logOutUser(),
store.cleanUp(),
somelib.whatever(),
]);
// done, let's leave.
this.setState({ finalized: true }), () => evt.target.click());
}
And that's it: when you click the link (that you styled to look like a button because that's what CSS is for) React checks if it can safely navigate away as a state check.
If it can, it lets that happen.
If it can't:
it prevents the navigation of occurring via preventDefault(),
does whatever work it needs to do, and then
marks itself as "it is safe to leave now", then retriggers the link.
You can try and create a link element and click it from code. This work for me
const navigateUrl = (url) => {
let element = document.createElement('a');
if(url.startsWith('http://') || url.startsWith('https://')){
element.href = url;
} else{
element.href = 'http://' + url;
}
element.click();
}
As pointed by #Mike 'Pomax' Kamermans, you can just use to navigate to external link.
I usually do it this way, with is-internal-link
import React from 'react'
import { Link as ReactRouterLink} from 'react-router-dom'
import { isInternalLink } from 'is-internal-link'
const Link = ({ children, to, activeClassName, ...other }) => {
if (isInternalLink(to)) {
return (
<ReactRouterLink to={to} activeClassName={activeClassName} {...other}>
{children}
</ReactRouterLink>
)
}
return (
<a href={to} target="_blank" {...other}>
{children}
</a>
)
}
export default Link
Disclaimer: I am the author of this is-internal-link
I had the same issue and my research into the issue uncovered that I could simply use an "a href" tag. If using target="_blank" you should write your link this...
Your Link
I couldn't find a simple way to do that with React Router. As #Mike wrote you should use anchor (<a> tags) when sending the user to external site.
I created a custom <Link> component to dynamically decide whether to render a React-Router <Link> or regular <a> tag.
import * as React from "react";
import {Link, LinkProps} from "react-router-dom";
const ReloadableLink = (props: LinkProps & { forceReload?: boolean }) => {
const {forceReload, ...linkProps} = props;
if (forceReload)
return <a {...linkProps} href={String(props.to)}/>;
else
return <Link {...linkProps}>
{props.children}
</Link>
};
export default ReloadableLink;