Export function from React functional component and use it elsewhere - javascript

I want to export function from one of my functional component that is using hooks to another one. I want to prevent redundant code appearing in my components.
I have tried to create separate function.js file where I wanted to place some of my functions but useDispatch hook makes it impossible as it throws hell a lot of errors in every attempt to make it work.
I was searching for solution and trying some export statements in different combinations.
What I want to do is to export my toggleDrawer function from Layout component to other components and here's my code. I'm sure it's very easy and I'm missing something.
import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
import Header from '../Header/header'
import DrawerItems from '../DrawerItems/drawerItems'
import { REDUCERS } from '../../Config/config'
import Container from '#material-ui/core/Container'
import Drawer from '#material-ui/core/Drawer'
import { makeStyles } from '#material-ui/core/styles'
const useDrawerStyles = makeStyles({
paper: {
width: '175px',
padding: '10px'
}
})
const Layout = props => {
const { isDrawerOpened } = useSelector(state => {
return {
...state.interface_reducer
}
})
const dispatch = useDispatch()
const drawerClasses = useDrawerStyles()
const toggleDrawer = (side, open) => event => {
if (event.type === 'keydown' && (event.key === 'Tab' || event.key === 'Shift')) {
return null
}
dispatch({
type: REDUCERS.TOGGLE_DRAWER,
payload: open
})
}
return (
<Container>
<React.Fragment>
<Header/>
<Drawer classes={{paper: drawerClasses.paper}} open={isDrawerOpened} onClose={toggleDrawer('left', false)} >
<DrawerItems/>
</Drawer>
{ props.children }
</React.Fragment>
</Container>
)
}
export default Layout

Define the function in another file. Or define it in that file and export it. Then you can import it in other files for other components.

Related

React nextJS function references it self and pulls data without an import

I bought this template and am trying to understand which page getLayout in this code"{getLayout(<Component {...pageProps} />)}" goes to on initial load. I'm guessing it's a global variable somewhere, but I can't find it using the definitions. I'm trying to under the next.js documentation, but I'm having issues. If anyone has a good tutorial for this I'll happily take it.
import type { AppProps } from 'next/app';
import { appWithTranslation } from 'next-i18next';
import { SessionProvider } from 'next-auth/react';
import '#/assets/css/main.css';
import 'react-toastify/dist/ReactToastify.css';
import { ToastContainer } from 'react-toastify';
import { ModalProvider } from '#/components/ui/modal/modal.context';
import ManagedModal from '#/components/ui/modal/managed-modal';
import ManagedDrawer from '#/components/ui/drawer/managed-drawer';
import DefaultSeo from '#/components/seo/default-seo';
import { SearchProvider } from '#/components/ui/search/search.context';
import PrivateRoute from '#/lib/private-route';
import { CartProvider } from '#/store/quick-cart/cart.context';
import SocialLogin from '#/components/auth/social-login';
import { NextPageWithLayout } from '#/types';
import QueryProvider from '#/framework/client/query-provider';
import { getDirection } from '#/lib/constants';
import { useRouter } from 'next/router';
type AppPropsWithLayout = AppProps & {
Component: NextPageWithLayout;
};
function CustomApp({
Component,
pageProps: { session, ...pageProps },
}: AppPropsWithLayout) {
// Use the layout defined at the page level, if available
const getLayout = Component.getLayout ?? ((page) => page);
const authenticationRequired = Component.authenticationRequired ?? false;
const { locale } = useRouter();
const dir = getDirection(locale);
return (
<div dir={dir}>
<SessionProvider session={session}>
<QueryProvider pageProps={pageProps}>
<SearchProvider>
<ModalProvider>
<CartProvider>
<>
<DefaultSeo />
{authenticationRequired ? (
<PrivateRoute>
{getLayout(<Component {...pageProps} />)}
</PrivateRoute>
) : (
getLayout(<Component {...pageProps} />)
)}
<ManagedModal />
<ManagedDrawer />
<ToastContainer autoClose={2000} theme="colored" />
<SocialLogin />
</>
</CartProvider>
</ModalProvider>
</SearchProvider>
</QueryProvider>
</SessionProvider>
</div>
);
}
export default appWithTranslation(CustomApp);
Basically, you can define a per component layout. In order to do so, when you are defining a component, you add a property named getLayout. Here's an example, for a better understanding.
// ...
const Component: React.FC = () => <div>
I have a custom layout
</div>
// Here we define a layout, let's imagine it's a component
// we have inside /layouts and we have previously imported it
Component.getLayout = (page: React.ReactElement) =>
<LayoutComponent>{page}</LayoutComponent>
Now, when a page is rendered (note that in a Next JS app, all pages are rendered as a children of what is inside _app.{js,jsx,ts,tsx}) your code checks if the prop getLayout has been defined or not. Then, it is basically calling such function, if exists, otherwise it renders the base component.

Call component function that inside a page from another page

I'm trying to understand how I can trigger OnSwipe from inside another JS file called CardFooter.js containing a button with OnClick triggering OnSwipe from the previous JS file called CardItem.js. They both are called inside Card.js.
I'm learning Nextjs with Reactjs components. Any insight would greatly be appreciated
CardFooter.js
import { CardItem } from '../components/CardItem'
const CardFooter = () => {
return (
<Button onClick={() => CardItem.onSwipe('left')}></Button>;
<Button onClick={() => CardItem.onSwipe('right')}></Button>;
)
}
export default CardFooter
CardItem.js
import { useState, useContext, createContext } from 'react'
import { Context } from '../context/Context'
import TinderCard from 'react-tinder-card'
import { test } from '../components/CardFooter';
export const CardItem = ({ card }) => {
const { handleRightSwipe, handleLeftSwipe, currentAccount } = useContext(Context)
const onSwipe = dir => {
if (dir === 'right') {
handleRightSwipe(card, currentAccount)
}
if (dir === 'left') {
handleLeftSwipe(card, currentAccount)
}
}
return (
<TinderCard
preventSwipe={['up', 'down']}
onSwipe={onSwipe}
>
<div style={{ backgroundImage: `url('${card.imageUrl}')` }}>
<div>
{card.name}
</div>
</div>
</TinderCard>
)
}
export default CardItem
Card.js
import { useContext } from 'react'
import { Context } from '../context/Context'
import CardFooter from './CardFooter'
import CardItem from './CardItem'
const Card = () => {
const { cardsData } = useContext(Context)
return (
<div className={style.wrapper}>
<div className={style.cardMain}>
<div className={style.swipesContainer}>
{cardsData.map((card, index) => (
<CardItem card={card} key={index} />
))}
</div>
</div>
<CardFooter />
</div>
)
}
export default Card
In React, you can't pass props up from child components into parent components. This means that you wouldn't be able to pass the handleSwipe function up to <CardFooter>.
But it doesn't look like you'd need to do that anyways, since you have the handleRightSwipe and handleLeftSwipe functions within your Context, and all the handleSwipe function is really doing is calling those functions.
So, with this in mind, there are two solutions:
In your footer, import the context and use it, then when the user clicks on the right button, call handleRightSwipe, and handleLeftSwipe for the left button. (this is the naive solution)
Define handleSwipe within the parent <Card> component, then pass it as a prop to both of the components. This way, you only need to define the function once. (ideal solution)

Combine hooks without using Provider

I'm trying to combine several contexts that are feed with some async operations, in my app's pages.
I would like to combine these contexts without using the Context.Provider because it could be verbose. For example,
<Route path="/discover">
<MainContainer extraClass="discover-container" hasHeader={true}>
<UserContext>
<ContentContextProvider>
<NotificationContext>
<Discover />
</NotificationContext>
</ContentContextProvider>
</UserContext>
</MainContainer>
</Route>
In each of these Context I wrapper the child with the context. Fe,
import React from "react";
import useAllContent from "utils/hooks/useAllContent";
const ContentContext = React.createContext({});
export const ContentContextProvider = ({ children }) => {
const { allContent, setAllContents } = useAllContent([]);
return (
<ContentContext.Provider value={{ allContent, setAllContents }}>
{children}
</ContentContext.Provider>
);
};
export default ContentContext;
This works, but as I mentioned before is very verbose so i would like to use the Contexts like an objetcs to combine between them.
I tried:
import { useState, useEffect, useCallback } from "react";
import { DataStore, Predicates } from "#aws-amplify/datastore";
import { Content } from "models";
const useAllContent = (initialValue) => {
const [allContent, setContent] = useState(initialValue);
const setAllContents = useCallback(async () => {
const contents = await DataStore.query(Content, Predicates.ALL);
setContent(contents);
}, []);
useEffect(() => {
if (allContent === 0) setAllContents();
}, [allContent, setAllContents]);
return { allContent, setAllContents };
};
export default useAllContent;
import React from "react";
import useAllContent from "utils/hooks/useAllContent";
const { allContent, setAllContents } = useAllContent([]);
const ContentContext = React.createContext({ allContent, setAllContents });
export default ContentContext;
But I break the rule × Error: Invalid hook call. Hooks can only be called inside of the body of a function component.
How could i achieve it?
Combining instances of React.Context in the way you describe would require manipulating the values they contain. But there is no way to access the value of a Context without using its corresponding Provider. I sympathize with the dislike of verboseness, but it is unavoidable in this case.

Why is useEffect being triggered without dependency change when working with modals?

I'm having trouble working with useEffect to fetch comments when using a modal. I have a PostMain component that is displayed inside a modal, as seen below. Inside this, there is a CommentsList child component that fetches comments left under the post from the server. I have created a custom hook to handle this, as seen below. The problem I'm facing is whenever I exit the modal, then reopen it, useEffect is triggered even though its dependencies (pageNumber, postId) haven't changed. A server request similar to the initial one is sent, with the same comments being added to the list, as seen in the screenshots below. Obviously, this is not ideal. So, what am I doing wrong? How do I fix this?
Fetch Comments Custom Hook
import { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { fetchComments } from '../store/comments/actions';
function useFetchComments(pageNumber, commentsPerRequest = 5, postId) {
const { error, hasMoreComments, isLoading, commentList } = useSelector(
({ comments }) => ({
error: comments.error,
hasMoreComments: comments.hasMoreComments,
isLoading: comments.isLoading,
commentList: comments.commentList,
})
);
const currentCommentListLength = commentList.length || 0;
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchComments(pageNumber, commentsPerRequest, currentCommentListLength, postId));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [pageNumber, postId]);
return {
error,
hasMoreComments,
isLoading,
commentList,
};
}
export default useFetchComments;
Post Component
import React from 'react';
import { useSelector } from 'react-redux';
import { Image, Modal } from 'semantic-ui-react';
import CommentForm from '../../forms/comment';
import CommentList from '../../shared/comment-list';
function PostMain({ post }) {
const { isLoggedIn } = useSelector(({ auth }) => ({
isLoggedIn: auth.isLoggedIn,
}));
return (
<Modal size="tiny" trigger={<Image src={post.url} />}>
<Modal.Content>
<div>
<Image src={post.url} />
<CommentList postId={post._id} />
{isLoggedIn && (
<CommentForm postId={post._id} />
)}
</div>
</Modal.Content>
</Modal>
);
}
export default PostMain;
Comment List Component
import React, { useState } from 'react';
import { useFetchComments } from '../../../hooks';
function CommentList({ postId }) {
const COMMENTS_PER_REQUEST = 5;
const [pageNumber, setPageNumber] = useState(1);
const { error, isLoading, commentList, hasMoreComments } = useFetchComments(
pageNumber,
COMMENTS_PER_REQUEST,
postId
);
const handleFetchMoreComments = () => {
setPageNumber((previousNumber) => previousNumber + 1);
};
return (
<div>
<div>
{commentList.map((comment) => (
<div key={comment._id}>{comment.body}</div>
))}
{hasMoreComments && (
<p onClick={handleFetchMoreComments}>View More</p>
)}
</div>
{isLoading && <p>Loading...</p>}
{error && <p>{JSON.stringify(error)}</p>}
</div>
);
}
export default CommentList;
First instance of opening modal
Second instance of opening modal

React: Context to pass state between two hierarchies of components

I am developing a website in which I want to be able to access the state information anywhere in the app. I have tried several ways of implementing state but I always get following error message:
Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
Check the render method of SOS.
Here is my SOS->index.js file:
import React, { useContext } from 'react';
import axios from 'axios';
import CONST from '../utils/Constants';
import { Grid, Box, Container } from '#material-ui/core';
import { styled } from '#material-ui/styles';
import { Header } from '../Layout';
import ListItem from './ListItem';
import SOSButton from './SOSButton';
import FormPersonType from './FormPersonType';
import FormEmergencyType from './FormEmergencyType';
import StateContext from '../App';
import Context from '../Context';
export default function SOS() {
const { componentType, setComponentType } = useContext(Context);
const timerOn = false;
//'type_of_person',
const ambulance = false;
const fire_service = false;
const police = false;
const car_service = false;
//static contextType = StateContext;
const showSettings = event => {
event.preventDefault();
};
const handleComponentType = e => {
console.log(e);
//this.setState({ componentType: 'type_of_emergency' });
setComponentType('type_of_emergency');
};
const handleEmergencyType = new_emergency_state => {
console.log(new_emergency_state);
// this.setState(new_emergency_state);
};
const onSubmit = e => {
console.log('in OnSubmit');
axios
.post(CONST.URL + 'emergency/create', {
id: 1,
data: this.state //TODO
})
.then(res => {
console.log(res);
console.log(res.data);
})
.catch(err => {
console.log(err);
});
};
let component;
if (componentType == 'type_of_person') {
component = (
<FormPersonType handleComponentType={this.handleComponentType} />
);
} else if (componentType == 'type_of_emergency') {
component = (
<FormEmergencyType
handleComponentType={this.handleComponentType}
handleEmergencyType={this.handleEmergencyType}
emergencyTypes={this.state}
timerStart={this.timerStart}
onSubmit={this.onSubmit}
/>
);
}
return (
<React.Fragment>
<Header title="Send out SOS" />
<StateContext.Provider value="type_of_person" />
<Container component="main" maxWidth="sm">
{component}
</Container>
{/*component = (
<HorizontalNonLinearStepWithError
handleComponentType={this.handleComponentType}
/>*/}
</React.Fragment>
);
}
I would really appreciate your help!
Just for reference, the Context file is defined as follows:
import React, { useState } from 'react';
export const Context = React.createContext();
const ContextProvider = props => {
const [componentType, setComponentType] = useState('');
setComponentType = 'type_of_person';
//const [storedNumber, setStoredNumber] = useState('');
//const [functionType, setFunctionType] = useState('');
return (
<Context.Provider
value={{
componentType,
setComponentType
}}
>
{props.children}
</Context.Provider>
);
};
export default ContextProvider;
EDIT: I have changed my code according to your suggestions (updated above). But now I get following error:
TypeError: Cannot read property 'componentType' of undefined
Context is not the default export from your ../Context file so you have to import it as:
import { Context } from '../Context';
Otherwise, it's trying to import your Context.Provider component.
For your file structure/naming, the proper usage is:
// Main app file (for example)
// Wraps your application in the context provider so you can access it anywhere in MyApp
import ContextProvider from '../Context'
export default () => {
return (
<ContextProvider>
<MyApp />
</ContextProvider>
)
}
// File where you want to use the context
import React, { useContext } from 'react'
import { Context } from '../Context'
export default () => {
const myCtx = useContext(Context)
return (
<div>
Got this value - { myCtx.someValue } - from context
</div>
)
}
And for godsakes...rename your Context file, provider, and everything in there to something more explicit. I got confused even writing this.

Categories

Resources