REACT JS - Passing values from one component to another - javascript

I have a NavBar.js component thats holds login info on the logged in user. When the user is logged in it says "Welcome" along with the user details. I want to implement the same function in ProductList.js component so when a user posts a blog, it says "Posted By: " along with the users log in details. How would I pass the details form NavBar.js to ProductList.js ?
import React, { useState, useEffect } from 'react';
import { NavLink } from 'react-router-dom';
const NavBar = (props) => {
const providers = ['twitter', 'github', 'aad'];
const redirect = window.location.pathname;
const [userInfo, setUserInfo] = useState();
useEffect(() => {
(async () => {
setUserInfo(await getUserInfo());
})();
}, []);
async function getUserInfo() {
try {
const response = await fetch('/.auth/me');
const payload = await response.json();
const { clientPrincipal } = payload;
return clientPrincipal;
} catch (error) {
console.error('No profile could be found');
return undefined;
}
}
return (
<div className="column is-2">
<nav className="menu">
<p className="menu-label">Menu</p>
<ul className="menu-list">
<NavLink to="/products" activeClassName="active-link">
Recipes
</NavLink>
<NavLink to="/about" activeClassName="active-link">
Help
</NavLink>
</ul>
{props.children}
</nav>
<nav className="menu auth">
<p className="menu-label">LOGIN</p>
<div className="menu-list auth">
{!userInfo &&
providers.map((provider) => (
<a key={provider} href={`/.auth/login/${provider}?post_login_redirect_uri=${redirect}`}>
{provider}
</a>
))}
{userInfo && <a href={`/.auth/logout?post_logout_redirect_uri=${redirect}`}>Logout</a>}
</div>
</nav>
{userInfo && (
<div>
<div className="user">
<p>Welcome</p>
<p>{userInfo && userInfo.userDetails}</p>
<p>{userInfo && userInfo.identityProvider}</p>
</div>
</div>
)}
</div>
);
};
export default NavBar;
This is a snippet from ProductsList.js, where I want the user details data to be passed to:
<footer className="card-footer ">
<ButtonFooter
className="cancel-button"
iconClasses="fas fa-undo"
onClick={handleCancelProduct}
label="Cancel"
/>
<ButtonFooter
className="save-button"
iconClasses="fas fa-save"
onClick={handleSave}
label="Save"
/> Posted By: {}
</footer>

You have to use a global state manager like redux , Recoil , context-api etc. If the state receiver component is not a child component of state sender component, you can't use props. You may check Redux official documentation.

You can probably force your current set up to work using an event emitter, where your ProductList sends out an event requesting the user info, and the NavBar is listening for those events, and responds with the appropriate data.
I don't recommend this, though, with your current set up.
I recommend lifing your user info logic out of the NavBar and into app state that sits above both your NavBar and your ProductList in the component tree. The idea is that you'll have a store of app state data that sits near the root level of your app, to hold things that multiple parts of your app may want access to at different moments. In this case, your NavBar and your ProductList both care about user data.
This also allows the NavBar component focus on generating HTML rather than data fetching and storing. Separating these concerns assists greatly with your app growing over time (maybe you create another component down the road that also wants to know about User info), and then also things like testing so that you don't have to have a mock API in place to see if your NavBar is displaying the data it expects correctly.
What I'm suggesting will pay off in the end, but it does increase the complexity of what you're doing. You'll have to rearchitect a few pieces of your app to put a centralized data store in place. And create ways to add data to that store (a user logs in) and get data out of the store (components want to display user info).
There are several popular ways to handle this sort of app state store.
Redux taught me a lot about how to organize data flows and has a library react-redux to integrate directly with your React components
React has Context Provider and useContext hook, and even has a useReducer hook that allows you to re-create Redux using native React tools
RxJS I have not used personally, but it offers some benefits that the others do not.
There are many more. Google "React state libraries" to explore your options
I know this doesn't directly answer your question, but I hope it helps a bit nonetheless.

Related

React 18 Suspense Fallback not working on Data Fetching?

I am basically fetching a bunch of user data and displaying a list of their usernames on page load. I am using the suspense with the intention of displaying the "Loading..." text while the userList component is being processed, but for some reason it does'nt display the loading text mentioned inside the fallback. I am using the latest react 18 version.
import React, { useState, useEffect, Suspense } from "react";
function UsersList() {
// Fetches and returns List of users
const [users, setUsers] = useState([]);
useEffect(() => {
try {
fetch("https://jsonplaceholder.typicode.com/users")
.then((response) => response.json())
.then((data) => setUsers(data));
} catch (error) {
// handle the error here
}
}, []);
return (
<div>
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
}
function App() {
return (
<div className="App">
<h2>Users List</h2>
<Suspense fallback={<h1> Loading...</h1>}>
<h4> Below are the user detailed fetched : </h4>
<UsersList />
</Suspense>
</div>
);
}
export default App;
I tried using throttling to reduce network speed on chrome dev tool, but still the loading text is not being displayed.
How does Suspense is suppose to know that you're in the process of fetching the data?
From the docs:
Only Suspense-enabled data sources will activate the Suspense component.
They include:
Data fetching with Suspense-enabled frameworks like Relay and Next.js
Lazy-loading component code with lazy
Suspense does not detect when data is fetched inside an Effect or event handler.
Suspense-enabled data fetching without the use of an opinionated framework is not yet supported. The requirements for implementing a Suspense-enabled data source are unstable and undocumented. An official API for integrating data sources with Suspense will be released in a future version of React.
Tl;DR: You can't easily use it by yourself yet.

calling setState only once inside of useEffect--is there a better method?

In my react app I use the following pattern quite a bit:
export default function Profile() {
const [username, setUsername] = React.useState<string | null>(null);
React.useEffect(()=>{
fetch(`/api/userprofiles?username=myuser`)
.then(res=>res.json())
.then(data => setUsername(data.username))
},[])
return(
<div>
{username}'s profile
</div>
)
}
When the page loads, some user data is fetched from the server, and then the page updates with that user data.
One thing I notice is that I only really need to call setUsername() once on load, which makes using state seem kinda excessive. I can't shake the feeling that there must be a better way to do this in react, but I couldn't really find an alternative when googling. Is there a more efficient way to do this without using state? Or is this the generally agreed upon way to load data when it only needs to be done once on page load
Without using any external libraries, no - that is the way to do it.
It would be possible to remove the state in Profile and have it render the username from a prop, but that would require adding the state into the parent component and making the asynchronous request there. State will be needed somewhere in the app pertaining to this data.
The logic can be abstracted behind a custom hook. For example, one library has useFetch where you could do
export default function Profile() {
const { data, error } = useFetch('/api/userprofiles?username=myuser');
// you can check for errors if desired...
return(
<div>
{data.username}'s profile
</div>
)
}
Now the state is inside useFetch instead of in your components, but it's still there.

Best practice for Next.js data fetching inside a component

I have a menu component that appears globally. What is the best practice for getting data into that component?
I'm trying to take advantage of static generation that Next.js offers but all data fetching guidance from the Next.js team relates to pages. getStaticProps and getStaticPaths seem to pertain to page generation, not data for components. Is their SWR package the right answer, or Apollo Client?
Typically in hooks-based React, I'd just put my data call into useEffect. I'm not sure how to reason this out being that everything is rendered at build time with Next.
This is such a tricky problem, I think we need to lay out some background before a solution comes into focus. I'm focusing in the React.js world but a lot of this would apply to Vue/Nuxt I'd imagine.
Background / Static Generation Benefits:
Gatsby and Next are focused on generating static pages, which vastly improves performance and SEO in React.js sites. There is a lot of technical overhead to both platforms beyond this simple insight but let's start with this idea of a digital machine pumping out fancy HTML pages for the browser.
Data Fetching for Pages
In the case of Next.js (as of v9.5), their data fetching mechanism getStaticProps does most of the heavy lifting for you but it's sandboxed to the /pages/ directory. The idea is that it does the data fetching for you and tells the Next.js page generator in Node about it during build time (instead of doing it component-side in a useEffect hook - or componentDidMount). Gatsby does much the same with their gatsby-node.js file, which orchestrates the data fetching for page building in concert with a Node server.
What about Global Components that need data?
You can use both Gatsby and Next to produce any kind of website but a huge use case are CMS-driven websites, because so much of that content is static. These tools are an ideal fit to that use case.
In typical CMS sites, you will have elements that are global - header, footer, search, menu, etc. This is where static generation faces a big challenge: how do I get data into dynamic global components at build time? The answer to this question is... you don't. And if you think about this for a minute it makes sense. If you had a 10K page site, would you want to trigger a site-wide rebuild if someone adds a new nav item to a menu?
Data Fetching for Global Components
So how do we get around this? The best answer I have is apollo-client and to do the fetch client side. This helps us for a number of reasons:
For small size queries, the performance impact is negligible.
If we need to rebuild pages for changes at the CMS layer, this slides by Next/Gatsby's detection mechanisms, so we can make global changes without triggering gigantic site-wide rebuilds.
So what does this actually look like? At the component level, it looks just like a regular Apollo-enhanced component would. I usually use styled-components but I tried to strip that out so you can could better see what's going on.
import React from 'react'
import { useQuery, gql } from '#apollo/client'
import close from '../public/close.svg'
/**
* <NavMenu>
*
* Just a typical menu you might see on a CMS-driven site. It takes in a couple of props to move state around.
*
* #param { boolean } menuState - lifted state true/false toggle for menu opening/closing
* #param { function } handleMenu - lifted state changer for menuState, handles click event
*/
const NAV_MENU_DATA = gql`
query NavMenu($uid: String!, $lang: String!) {
nav_menu(uid: $uid, lang: $lang) {
main_menu_items {
item {
... on Landing_page {
title
_linkType
_meta {
uid
id
}
}
}
}
}
}
`
const NavMenu = ({ menuState, handleMenu }) => {
// Query for nav menu from Apollo, this is where you pass in your GraphQL variables
const { loading, error, data } = useQuery(NAV_MENU_DATA, {
variables: {
"uid": "nav-menu",
"lang": "en-us"
}
})
if (loading) return `<p>Loading...</p>`;
if (error) return `Error! ${error}`;
// Destructuring the data object
const { nav_menu: { main_menu_items } } = data
// `menuState` checks just make sure out menu was turned on
if (data) return(
<>
<section menuState={ menuState }>
<div>
{ menuState === true && (
<div>Explore</div>
)}
<div onClick={ handleMenu }>
{ menuState === true && (
<svg src={ close } />
)}
</div>
</div>
{ menuState === true && (
<ul>
{ data.map( (item) => {
return (
<li link={ item }>
{ item.title }
</li>
)
})}
</ul>
)}
</section>
</>
)
}
export default NavMenu
Set Up for Next to Use Apollo
This is actually really well documented by the Next.js team, which makes me feel like I'm not totally hacking the way this tool is supposed to work. You can find great examples of using Apollo in their repo.
Steps to get Apollo into a Next app:
Make a custom useApollo hook that sets up the connection to your data source (I put mine in /lib/apollo/apolloClient.js within Next's hierarchy but I'm sure it could go elsewhere).
import { useMemo } from 'react'
import { ApolloClient, InMemoryCache, SchemaLink, HttpLink } from '#apollo/client'
let apolloClient
// This is mostly from next.js official repo on how best to integrate Next and Apollo
function createIsomorphLink() {
// only if you need to do auth
if (typeof window === 'undefined') {
// return new SchemaLink({ schema })
return null
}
// This sets up the connection to your endpoint, will vary widely.
else {
return new HttpLink({
uri: `https://yourendpoint.io/graphql`
})
}
}
// Function that leverages ApolloClient setup, you could just use this and skip the above function if you aren't doing any authenticated routes
function createApolloClient() {
return new ApolloClient({
ssrMode: typeof window === 'undefined',
link: createIsomorphLink(),
cache: new InMemoryCache(),
})
}
export function initializeApollo(initialState = null) {
const _apolloClient = apolloClient ?? createApolloClient()
// If your page has Next.js data fetching methods that use Apollo Client, the initial state
// gets hydrated here
if (initialState) {
// Get existing cache, loaded during client side data fetching
const existingCache = _apolloClient.extract()
// Restore the cache using the data passed from getStaticProps/getServerSideProps
// combined with the existing cached data
_apolloClient.cache.restore({ ...existingCache, ...initialState })
}
// For SSG and SSR always create a new Apollo Client
if (typeof window === 'undefined') return _apolloClient
// Create the Apollo Client once in the client
if (!apolloClient) apolloClient = _apolloClient
return _apolloClient
}
// This is goal, now we have a custom hook we can use to set up Apollo across our app. Make sure to export this!
export function useApollo(initialState) {
const store = useMemo(() => initializeApollo(initialState), [initialState])
return store
}
Modify _app.js in the /pages/ directory of Next. This is basically the wrapper that goes around every page in Next. We're going to add the Apollo provider to this, and now we can globally access Apollo from any component.
import { ApolloProvider } from '#apollo/react-hooks'
import { useApollo } from '../lib/apollo/apolloClient'
/**
* <MyApp>
*
* This is an override of the default _app.js setup Next.js uses
*
* <ApolloProvider> gives components global access to GraphQL data fetched in the components (like menus)
*
*/
const MyApp = ({ Component, pageProps }) => {
// Instantiates Apollo client, reads Next.js props and initialized Apollo with them - this caches data into Apollo.
const apolloClient = useApollo(pageProps.initialApolloState)
return (
<ApolloProvider client={ apolloClient }>
<Component {...pageProps} />
</ApolloProvider>
)
}
export default MyApp
And now you can get dynamic data inside of your components using Apollo! So easy right ;) HA!
For global data fetching in NextJS, I use react-query and there is no need for a global state because it lets you to cache the data. Let's say you have a blog with categories and you want to put the category names in the navbar as a dropdown menu. In this case you can call the API to fetch the data with react-query from the navbar component and cache it. The navbar data will be available for all pages.

useMemo is not working when sessionStorage is updated

I have an app which header contains icon which should be shown when the user is logged in. I keep my logged in info in sessionStorage but when it changes my component is not rendered again. I tried to use useEffect for that and useMemo but it doesn't worked.
The updating part:
const isLoggedIn = useMemo(() => sessionStorage.getItem('isLogged'), [sessionStorage.getItem('isLogged')]);
The usage:
{isLoggedIn === 'true' ? ['left'].map((anchor) => (
...some jsx
)) : null}
The sessionStorage value is a string: "false" or "true".
I have routes and constant header, the header is not a part of routes so when it changes my header is not rerenders so I tried to use useMemo for that.
Posting my answer as per clarification gained through comments.
If you are using Redux:
I would recommend to store the user logged-in information in redux store and connect to the isolated Header component via connect HOC and mapStateToProps. Whenever you update (upon successful user login) the user login status the component will listen to store updates.
Or
You can use React context approach if there is no redux used
// Declare it outside of your App component/any other file and export it
const GlobalState = React.createContext();
// Declare state variable to store user logged in info inside of your App component
const [isLoggedIn, setIsLoggedIn] = useState(false);
// Add them to context to access anywhere in your components via useContext
// In App render or where you have route mapping
<GlobalState.Provider value={{
isLoggedIn,
setIsLoggedIn
}}>
....
</GlobalState.Provider>
// Update the status using setIsLoggedIn upon successful login where you are making login call
// In your Header get it via useContext
const context = useContext(GlobalState);
`context.isLoggedIn` is what you need.
// You can use useEffect/useMemo approach to get the login status updates
Find more about React context and useContext
sessionStorage is not an observer object and you have to store the current authentication state into a variable or React state and use that variable in your component. And when you authenticated the user, you should update the variable to true and change that to false when the user logged out.
To implement what I said, you can get help from these ways:
Redux
React context
You can implement the React context by your self from scratch or using the React-hooks-global-state
UseMemo is used for memoizing calculated values. You should be using useCallback.useCallback is used for memoizing function references.
Refer this
const isLoggedIn = useCallback(() => sessionStorage.getItem('isLogged'), [sessionStorage.getItem('isLogged')]);
Can you try to put your sessionStorage data into State and update that state? As far as I know, react will not know about the session storage. So even if you change the manipulate the data in the sessionStorage directly it won't gonna update your UI.
let [storeData, setStoreData] = useState(true);
let isLoggedIn = useMemo(() => ({ sessionData: storeData }), [storeData]);
{isLoggedIn === 'true' ? ['left'].map((anchor) => (
...some jsx
)) : null}
<button
onClick={() => {
sessionStorage.setItem("isLogged", !storeData);
setStoreData(sessionStorage.getItem("isLogged"));
}} > Update Store </button>

React and Redux losing State

Hope you all are fine. I am new to react redux world. I am learning and working on a call logging project. I have a few questions and it would great if someone can guide me whether I am doing it wrong or tell me the alternative.
I am using JWT to authenticate a user. Once the user details are verified. I am dispatching success action and in the reducer, I am setting the state to authenticated true and the response. I am also storing the token and expiryTime in localStorage
In the root file which is index file. I am checking if the token exists in localStorage and if so then dispatching sign in action.
Everything is working. But I am losing other values like a response from a server. When he logged in for the first time. How can I tackle this problem ?
Secondly, there is a User initial icon on the top right corner. I get the initial when a user logs in and it gets stored in auth state. it works fine but once again if I refresh the page it becomes null and I lose that initial.
so I tried another way and stored the initial in localStorage. But the navbar already rendered on the screen and I don't see any initial until I refresh the page.
I have passed new key in mapStateToProps. ii and stored initial from localStorage in it and it working fine. Is this a valid way of doing it ???
Regards
Meet
const SignedInLinks = (props) => {
return (
<ul className="right">
<li><NavLink to="/signin" onClick=
{props.signOut}>Log Out</NavLink></li>
<li><NavLink className="btn btn-floating pink lighten-1" to="/">
{props.ii ? props.ii : null }
</NavLink></li>
</ul>
)}
const mapStateToProps = state => {
return {
auth: state.auth,
ii: window.localStorage.getItem('ui')
}
}
export default connect(mapStateToProps, { signOut })(SignedInLinks);
Rather than using localStorage in mapStateToProps, intialize your ii state in your reducer corresponding to that state and then pass it to your component via mapStateToProps. Something like this.
const iiReducer = (state = window.localStorage.getItem('ui') || false, action) => {
/*.
.
. Other Logic
.
.*/
return state
}
and then use it normally as you would from a store's state
const mapStateToProps = state => {
return {
auth: state.auth,
ii: state.ii
}
}
Hope this helps !
I believe I have an idea of what the problem is (I'm kind of a beginner in react and redux aswell, so tell me if I'm speaking nonsense).
You say that you store the token in localstorage (it is recommended not to do that btw, the recommended way is to store it in a cookie), and if a valid token is found in localstorage, you log in. I'm guessing that you store the response from the server (including the icon) in the app's state, or in the redux store? If that is the case, this information will be removed when you update the browser (f5),therefore not being loaded anymore.
The solution is to make sure to load the relevant data when the component mounts, so in your componentDidMount method (if you don't have one, make one), and set the state in that method.

Categories