ReactJS hooks: how to useContext in two different .js files? - reactjs

I have two components in the same .js file using React.createContext() and React.useContext(). Now I would like to move the Menu component in another .js file but I'm having trouble using the same context. This is the code:
const ManagerContext = React.createContext(null);
export default function LessonManager() {
const [title, setTitle] = React.useState('SomeOtherTitle');
const [editing, toggleEditor] = React.useState(false);
const value = React.useMemo(() => {
return {
title,
setTitle,
editing,
toggleEditor,
log: (t) => console.log(t)
}
}, [title, editing]);
return (
<ManagerContext.Provider value={value}>
<div className='box-default expand'>
<div className='handle' style={{display: 'flex', justifyContent: 'center', width: '100%', cursor: 'grab'}}>
<LessonMenu/>
</div>
</div>
</ManagerContext.Provider>
)
}
export function LessonMenu() {
const state = React.useContext(ManagerContext);
return (
<ButtonGroup size='sm'>
<IconButton
className='rsuite-btn menu-button'
onClick={()=>state.toggleEditor(!state.editing)}
icon={ <Icon icon={state.editing ? ('eye') : ('edit2')} />} />
</ButtonGroup>
)
}
I tried to export the const ManagerContext:
export const ManagerContext = React.createContext(null);
and import it in the Menu.js file I created with the Menu component, but it doesn't work. What am I doing wrong?

Your code seems to work, note how you need to import the context:
// export const ManagerContext = React.createContext(null);
import { ManagerContext } from "./Menu.js";
export function LessonMenu() {
const { toggleEditor, editing } = React.useContext(ManagerContext);
return (
<ButtonGroup size="sm">
<IconButton
className="rsuite-btn menu-button"
onClick={() => toggleEditor(!editing)}
icon={<Icon icon={toggleEditor ? "eye" : "edit2"} />}
/>
</ButtonGroup>
);
}

Related

react populate modal from graphql

I have a list of products in a react component:
I am trying to display a modal when I click on the specific product that displays the product information through a graphql query. The query works fine. I'm just having problems displaying the modal.
Nothing happens when I click except my console.log statement. DisplayProduct never really gets loaded except when the page is loaded. How do I trigger a reload of DisplayProduct on click.
I'm pretty new to react, hence the issue...
ProductList.jsx
/* eslint-disable no-unused-vars */
import { useState, useCallback } from "react";
import { ResourceList, TextStyle, Stack, Thumbnail } from "#shopify/polaris";
import { Layout } from "#shopify/polaris";
import { DisplayProduct } from "./DisplayProduct";
export function ProductsList({ data }) {
const [open, setOpen] = useState(false);
const [clickedItem, setClickedItem] = useState(false);
const [active, setActive] = useState(false);
return (
<Layout>
<ResourceList // Defines your resource list component
showHeader
resourceName={{ singular: `Product`, plural: `Products` }}
items={data.nodes}
renderItem={(item) => {
const media = (
<Thumbnail
source={
item.images.edges[0] ? item.images.edges[0].node.originalSrc : ``
}
alt={item.images.edges[0] ? item.images.edges[0].node.altText : ``}
/>
);
const price = item.variants.edges[0].node.price;
return (
<ResourceList.Item
id={item.id}
media={media}
accessibilityLabel={`View details for ${item.title}`}
onClick={() => {
setClickedItem(item.id);
setOpen(true);
console.log(`itemId`, clickedItem);
}}
onCancel={() => setOpen(false)}
>
<Stack>
<Stack.Item fill>
<h3>
<TextStyle variation="strong">{item.title}</TextStyle>
</h3>
</Stack.Item>
<Stack.Item>
<p>${price}</p>
</Stack.Item>
</Stack>
</ResourceList.Item>
);
}}
/>
<Layout.Section>
<div style={{ height: `500px` }}>
<Modal
onClose={() => {
setActive(false);
setOpen(false);
console.log(`clicked close button: `, { active });
}}
title="Display Product"
open={active}
loading={false}
limitHeight={false}
sectioned={true}
show={active}
>
<Modal.Section>
<TextContainer>
<DisplayProduct productId={clickedItem} />
</TextContainer>
</Modal.Section>
</Modal>
</div>
</Layout.Section>
</Layout>
);
}
DisplayProduct.jsx
/* eslint-disable no-unused-vars */
import { useState, useCallback } from "react";
import { Banner, Button, Modal, TextContainer } from "#shopify/polaris";
import { gql, useQuery } from "#apollo/client";
import { Loading } from "#shopify/app-bridge-react";
const GET_PRODUCT_BY_ID = gql`
query getProductsById($id: ID!) {
product(id: $id) {
createdAt
defaultCursor
description
descriptionHtml
vendor
}
}
`;
export function DisplayProduct({ productId }) {
const [active, setActive] = useState(true);
const { loading, error, data, refetch } = useQuery(GET_PRODUCT_BY_ID, {
variables: { id: productId },
});
if (loading || typeof data === `undefined`) {
console.log(`loading`);
return <Loading />;
}
if (error) {
console.warn(error);
return (
<Banner status="critical">There was an issue loading products.</Banner>
);
}
console.log(`productId: `, productId);
console.log(`data: `, data);
return (
<p>
display our product here {data.id} and {productId}
</p>
);
}
New to react is right!
I needed a conditional display for the modal. If open is true, display the modal. When the modal is closed, the dom element is removed and then reloaded when open is set to true. I'm not sure if I can refine that down even further so it doesn't have to rebuild the modal each time a product is clicked
Here is the complete .jsx:
import { useState, useCallback } from "react";
import {
ResourceList,
TextStyle,
Stack,
Thumbnail,
Button,
Modal,
TextContainer,
Layout,
} from "#shopify/polaris";
import { DisplayProduct } from "./DisplayProduct";
export function ProductsList({ data }) {
const [open, setOpen] = useState(false);
const [clickedItem, setClickedItem] = useState(false);
const { prodData, setProdData } = useState(false);
return (
<Layout>
<ResourceList // Defines your resource list component
showHeader
resourceName={{ singular: `Product`, plural: `Products` }}
items={data.nodes}
renderItem={(item) => {
const media = (
<Thumbnail
source={
item.images.edges[0] ? item.images.edges[0].node.originalSrc : ``
}
alt={item.images.edges[0] ? item.images.edges[0].node.altText : ``}
/>
);
const price = item.variants.edges[0].node.price;
return (
<ResourceList.Item
id={item.id}
media={media}
accessibilityLabel={`View details for ${item.title}`}
onClick={() => {
setClickedItem(item.id);
setOpen(true);
}}
onCancel={() => setOpen(false)}
>
<Stack>
<Stack.Item fill>
<h3>
<TextStyle variation="strong">{item.title}</TextStyle>
</h3>
</Stack.Item>
<Stack.Item>
<p>${price}</p>
</Stack.Item>
</Stack>
</ResourceList.Item>
);
}}
/>
<Layout.Section> /* here is the important part. Conditionally load the modal and therefore, DisplayProduct. */
{open ? (
<div style={{ height: `500px` }}>
<Modal
onClose={() => {
setOpen(false);
}}
title="Display Product"
open={open}
loading={false}
limitHeight={false}
sectioned={true}
>
<Modal.Section>
<TextContainer>
<DisplayProduct productId={clickedItem} />
</TextContainer>
</Modal.Section>
</Modal>
</div>
) : (
``
)}
</Layout.Section>
</Layout>
);
}

I want split one component in 2 components React JS

I'v a problem because I would split one component in two components.
I build my component with a search bar with autocomplete and the map with a pin chosen with the search bar. And me, I would one component with searcbar and an other with map who comunicate the date between them.
But I don't no how make this ..
Somebody could help me ?
Sorry for my Enlish, I'm French
Here is the code of my component:
import React, { useState, createContext } from 'react';
import {Map, Marker, GoogleApiWrapper} from 'google-maps-react';
import PlacesAutocomplete, {
geocodeByAddress,
getLatLng,
} from 'react-places-autocomplete';
import Child2 from '../Child2';
const Location = createContext();
function Searchbar() {
const [address, setAdress] = useState("")
const [coordinates, setCoordinates] = useState({
lat: null,
lng: null
})
const handleSelect = (value, props) => {
const results = await geocodeByAddress(value);
const ll = await getLatLng(results[0])
console.log(ll)
setAdress(value)
setCoordinates(ll)
props.OnSelectPlace(ll)
}
return (
<div className="Searchbar">
<p>{coordinates.lat}</p>
<p>{coordinates.lng}</p>
<p>{address}</p>
<PlacesAutocomplete
value={address}
onChange={setAdress}
onSelect={handleSelect}
>
{({ getInputProps, suggestions, getSuggestionItemProps, loading }) => (
<div>
<input
{...getInputProps({
placeholder: 'Search Places ...',
className: 'location-search-input',
})}
/>
<div className="autocomplete-dropdown-container">
{loading && <div>Loading...</div>}
{suggestions.map(suggestion => {
const className = suggestion.active
? 'suggestion-item--active'
: 'suggestion-item';
// inline style for demonstration purpose
const style = suggestion.active
? { backgroundColor: '#E4E4E4', cursor: 'pointer' }
: { backgroundColor: '#F8F8F8', cursor: 'pointer' };
return (
<div
{...getSuggestionItemProps(suggestion, {
className,
style,
})}
>
<span>{suggestion.description}</span>
</div>
);
})}
</div>
</div>
)}
</PlacesAutocomplete>
</div>
);
}
export {Location};
export default GoogleApiWrapper({
apiKey: ("secret_code_api_google_map")
})(Searchbar)
Build two separate components one for the map and other for the searchbar. Then pass date as a prop to the second component.

Trying to get Material ui dialog into contextualised modal

I have a modal component which I have put into context.
If I return html , some divs etc, then no problem, but
ideally I want to do this with the material-ui dialog.
But if I put in materail-ui dialog, as below,
then nothing is displayed.
The component:
import React, { useCallback, useEffect, useState } from 'react'
import {Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle} from "#material-ui/core";
import {useTranslation} from "react-i18next";
import {errorStyles} from "../errorStyle";
// Create a React context
const ModalContext = React.createContext(null);
//Declare the modal component
const Modal = ({ modal, unSetModal, errorClasses = errorStyles(),
title, subtitle, children, }) => {
useEffect(() => {
const bind = e => {
if (e.keyCode !== 27) {
return
}
if (document.activeElement && ['INPUT', 'SELECT'].includes(document.activeElement.tagName)) {
return
}
unSetModal()
}
document.addEventListener('keyup', bind)
return () => document.removeEventListener('keyup', bind)
}, [modal, unSetModal])
const { t } = useTranslation()
return (
<>
<Dialog
className={errorClasses.modal}
fullWidth
maxWidth='md'
aria-labelledby='max-width-dialog-title'
>
<DialogTitle id='max-width-dialog-title'
className={errorClasses.header}>{title}</DialogTitle>
<DialogContent>
<DialogContentText>{subtitle}</DialogContentText>
</DialogContent>
<DialogActions>
<button className={errorClasses.cancelButton}
onClick={unSetModal}>{t("Close")}</button>
</DialogActions>
</Dialog>
</>
)
}
const ModalProvider = props => {
const [modal, setModal] = useState()
const unSetModal = useCallback(() => {
setModal('')
}, [setModal])
return (
<ModalContext.Provider value={{ unSetModal, setModal }} {...props} >
{props.children}
{modal && <Modal modal={modal} unSetModal={unSetModal} />}
</ModalContext.Provider>
)
}
// useModal: shows and hides the Modal
const useModal = () => {
const context = React.useContext(ModalContext)
if (context === undefined) {
throw new Error('useModal must be used within a UserProvider')
}
return context
}
export { ModalProvider, useModal }
And in another component (an edit form)
import { useModal, ModalProvider } from '../context/modal-context'
.....
export function DeviceModal (props) {
const { setModal } = useModal()
...
some other markup
<div style={{ position: 'sticky', bottom:'0', width:'100%', height:'40px', paddingTop:'5px'}}>
<Grid container direction="row-reverse" item xs={12} alignItems="right">
<button className={classes.cancelButton}
onClick={() => { setModal(<></>)}} >
{'Cancel'}
</button>
</Grid>
</div>
Hope someone can help
Thanks
Answer:
By adding the following to the dialogprops, it is resolved.
open={true}
backdropComponent={Backdrop}
and also, getting it to display values from the parent.
const ModalProvider = props => {
const [modal, setModal] = useState();
const [title, setTitle] = useState();
const [errMsg, setErrMsg] = useState();
const unSetModal = useCallback(() => {
setModal('')
}, [setModal])
// Provide the Modals functions and state variables to the consuming parent component
return (
<ModalContext.Provider value={{ unSetModal, setModal, setTitle, setErrMsg }} {...props} >
{props.children}
{modal && <Modal modal={modal} unSetModal={unSetModal} title={title} errMsg={errMsg} />}
</ModalContext.Provider>
)}
In the parent use setTitle("yada yada") setErrMsg('more yerra yada");

How to match an element with data clicked in react hooks?

I am trying to create a modal that will display all the data on the element clicked.
For exemple, if i clicked the id(367) of a movie, it will display the title, the genres and the released date.
First i created a Context.js that will send data in the component Movie.js:
Context.js
import React, { useState, useEffect, createContext } from "react";
import { getAxios } from "../Function/index";
const MovieContext = createContext();
const MovieProvider = ({ children }) => {
const [datas, setDatas] = useState([]);
const [testdatas, testsetDatas] = useState([]);
const getMovie = async () => {
const data = await getAxios(
"https://api.themoviedb.org/3/movie/upcoming?api_key={}"
);
setDatas(data.results);
};
useEffect(() => {
getMovie();
}, []);
return (
<MovieContext.Provider value={{ datas, testdatas }}>{children}</MovieContext.Provider>
);
};
export { MovieContext, MovieProvider };
Movie.js
import React, { useContext } from "react";
import { MovieContext } from '../Context'
import { Card, Row, Tag } from 'antd'
const Main = () => {
const { datas } = useContext(MovieContext)
return (
<Row gutter={16}>
{datas.map((item, id) => (
<Card
key={id}
style={{ width: 300, margin: 10 }} bordered={true}
hoverable
>
<div>
<p style={{ textAlign: "left" }}>{item.title} <span style={{ float: "right" }}>{item.vote_average}</span></p>
</div>
<p><img src={`https://image.tmdb.org/t/p/w500/${item.poster_path}`} alt="#" width="200" height="200" /></p>
<Tag color="red"> {item.title}</Tag>
</Card>
))}
</Row >
);
};
export default Main;

Adding a dependency to useEffect() causes inifnite loop. But removing dependency causes component to not rerender when database updates

The following code caused useEffect() to be called infinitely. Why is that the case? Removing drawingboarditems from the useEffect dependencies array solves the infinite loop, but as a result DrawingBoard will not automatically rerender whenenver a user adds an item to the database.
DrawingBoard.jsx
export default function DrawingBoard() {
const [drawingboarditems, setdrawingboarditems] = useState([]);
const currentUser = useContext(CurrentUserContext);
const [loading, setLoading] = useState(true);
const classes = useStyles();
useEffect(() => {
if (currentUser) {
const items = [];
//retrieving data from database
db.collection("drawingboarditems")
.where("userID", "==", currentUser.id)
.get()
.then((query) => {
query.forEach((doc) => {
items.push({
id: doc.id,
...doc.data(),
});
});
setdrawingboarditems(items);
setLoading(false);
});
}
}, [currentUser, drawingboarditems]);
return (
<>
{loading == false ? (
<Container>
<Masonry
breakpointCols={breakpoints}
className="my-masonry-grid"
columnClassName="my-masonry-grid_column"
>
{drawingboarditems.map((item) => (
<div>
<Note item={item} />
</div>
))}
<Note form />
</Masonry>
</Container>
) : (
<div className={classes.root}>
<CircularProgress />
</div>
)}
</>
);
Note.jsx
import React from "react";
import { Card, CardHeader, IconButton, makeStyles } from "#material-ui/core";
import MoreVertIcon from "#material-ui/icons/MoreVert";
import Form from "./Form";
import CardWindow from "./CardWindow";
const useStyles = makeStyles((theme) => ({
card: {
backgroundColor: theme.palette.secondary.main,
margin: theme.spacing(1, 0),
},
}));
export default function Note({ item, form }) {
const classes = useStyles();
return (
<Card className={classes.card}>
{form ? (
<Form />
) : (
<CardHeader
action={
<CardWindow/>
}
title={item.title}
/>
)}
</Card>
);
}
Form.jsx
import React, { useContext } from "react";
import TextField from "#material-ui/core/TextField";
import { makeStyles } from "#material-ui/core/styles";
import AddCircleOutlineIcon from "#material-ui/icons/AddCircleOutline";
import IconButton from "#material-ui/core/IconButton";
import { db } from "../FireStore";
import { CurrentUserContext } from "../utils/Context";
const useStyles = makeStyles((theme) => ({
form: {
"& .MuiTextField-root": {
margin: theme.spacing(1),
width: "70%", // 70% of card in drawing board
},
},
}));
export default function Form() {
const classes = useStyles();
const [value, setValue] = React.useState("");
const currentUser = useContext(CurrentUserContext);
const handleChange = (event) => {
setValue(event.target.value);
};
const handleSubmit = (event) => {
if (value) {
event.preventDefault();
db.collection("drawingboarditems").add({
title: value,
userID: currentUser.id,
});
setValue("");
}
};
return (
<form className={classes.form} noValidate autoComplete="off">
<div>
<TextField
id="standard-textarea"
placeholder="Add item"
multiline
onChange={handleChange}
value={value}
/>
<IconButton aria-label="add" onClick={handleSubmit}>
<AddCircleOutlineIcon fontSize="large" />
</IconButton>
</div>
</form>
);
}
In your case I would move the logic to the DrawingBoard component, and would pass props to the children, so when a children adds an item, the main component would know to refresh the list of items.
Example (not tested):
Extract the logic to work with FireBase to functions. In that way they would be more re-usable, and would not add clutter to your code.
const drawingboarditemsCollection = 'drawingboarditems';
function getAllNotes(userID) {
return db.collection(drawingboarditemsCollection)
.where("userID", "==", userID)
.get()
.then((query) => {
return query.map(doc => {
items.push({
id: doc.id,
...doc.data(),
});
});
});
}
function addNote(userID, title) {
return db.collection(drawingboarditemsCollection).add({
title,
userID,
});
}
The DrawingBoard component should handle the connection with the server, and should pass functions as props to children:
export default function DrawingBoard() {
const [drawingboarditems, setdrawingboarditems] = useState([]);
const currentUser = useContext(CurrentUserContext);
const [loading, setLoading] = useState(true);
const classes = useStyles();
// add the logic to get notes by
const getNotes = useCallback(() => {
setLoading(true);
getAllNotes(currentUser.id)
.then(items => {
setdrawingboarditems(items);
})
.finally(=> {
setLoading(false);
});
}, [currentUser]);
// create the submit handler
const handleSubmit = value => {
addNote(currentUser.id, value)
.then(getNotes); // after adding a note update the items
}
// initial get notes, or when currentUser changes
useEffect(() => {
getNotes();
}, [getNotes]);
return (
<>
{loading == false ? (
<Container>
<Masonry
breakpointCols={breakpoints}
className="my-masonry-grid"
columnClassName="my-masonry-grid_column"
>
{drawingboarditems.map((item) => (
<div>
<Note item={item} />
</div>
))}
<Note form onSubmit={handleSubmit} />
</Masonry>
</Container>
) : (
<div className={classes.root}>
<CircularProgress />
</div>
)}
</>
);
}
Pass the onSubmit function to Form:
export default function Note({ item, form, onSubmit }) {
const classes = useStyles();
return (
<Card className={classes.card}>
{form ? (
<Form onSubmit={onSubmit} />
) : (
<CardHeader
action={
<CardWindow/>
}
title={item.title}
/>
)}
</Card>
);
}
Use onSubmit to handle the submission:
export default function Form({ onSubmit }) {
const classes = useStyles();
const [value, setValue] = React.useState("");
const handleChange = (event) => {
setValue(event.target.value);
};
const handleSubmit = (event) => {
if (value) {
event.preventDefault();
onSubmit(value); // let the parent handle the actual update
setValue("");
}
};
return ( ... );
}

Resources