React Context Provider with multiple values being updated in several places - javascript

I am new to React and Context and am trying to use a global context provider in React.
export const GlobalDataContext = React.createContext({ user: null, textSearchList:[] });
user is updated in the same file this way:
return (
<GlobalDataContext.Provider value={{ currUser: user, textSearchList: []}}>
{children}
</GlobalDataContext.Provider>
);
I want to use the same context provider to update the textSearchList for a search bar in another component like this:
<GlobalDataContext.Provider value={{textSearchList:this.state.splitSearchList}}>
<SearchBar
value={this.state.value}
onChange={(newValue) => {
this.setState({ value: newValue });
}}
onRequestSearch={() => {
this.setSplitList(this.state.value);
}}
style={{
margin: '0 auto',
maxWidth: 800
}}
/>
{children}
</GlobalDataContext.Provider>
The above code is calling this function:
setSplitList = (searchString) =>{
var splitString = this.state.value.split(" ");
this.state.splitSearchList= splitString;
}
I can see that this is not updating the global context because it does not cause a re-rendering at the consumer which is here:
<GlobalDataContext.Consumer >
{({textSearchList}) =>
<Query query={GET_POSTS} pollInterval={500}>
{({ data, loading }) => (
loading
? <Loading />
: <div>
{data.posts.filter(function(post){
console.log(`Filtering based on this: ${textSearchList}`);
return this.textContainsStrings(post.text, textSearchList)
}).map(post => (
<div key={post._id}>
<PostBox post={post} />
</div>
))}
</div>
)}
</Query>
}
</GlobalDataContext.Consumer>
Is this approach even possible? If so, then what might I be doing wrong?

Related

MUI : Out-of-range value `X` for the select component

I'm making a glossary where each of the definitions are a cards that can be flipped (CardFlip) I build an array where I send for each card, the data via props to my component "CardFlip" dealing with the actual construction of cards with my data
This is my first component sending everything :
<div>
{glossaire.map((carte, index) => (
<Fragment key={carte.dat_id}>
<CardFlip item={carte} tags={tags} />
</Fragment>
))}
</div>
First prop ,"item", contains information such as: a title, a definition, a search tag
Second prop, "tags", is the list of tags that a definition can have, a definition can have only one tag, right now only those tags are available : "Application", "Entreprise", "Technique" and "Télécom"
And here is the code for my second component (only the interesting part):
export default function CardFlip = ({ item, user, tags }) => {
// -- [Variables] --
// Flip front / back
const [isFlipped, setIsFlipped] = useState(false);
// Storage
const [titreDef, setTitreDef] = useState("");
const [definitionDef, setDefinitionDef] = useState("");
const [tagSelected, setTagSelected] = useState("");
// Flag for error
const [errorTitre, setErrorTitre] = useState(false);
const [errorDefinition, setErrorDefinition] = useState(false);
const [errorSelect, setErrorSelect] = useState(false);
console.log(item.dat_tag);
console.log(tags);
// -- [Handlers] --
// UPDATE
const handleTitre = (data) => {
setTitreDef(data);
setErrorTitre(false);
};
const handleDefinition = (data) => {
setDefinitionDef(data);
setErrorDefinition(false);
};
const handleSelect = (event) => {
const {
target: { value },
} = event;
setTagSelected(value);
setErrorSelect(false);
}
return (
<Grow in style={{ transformOrigin: "0 0 0" }} {...{ timeout: 1000 }}>
<div style={{ display: "flex", padding: "10px" }}>
<ReactCardFlip
isFlipped={isFlipped}
flipDirection="horizontal"
style={{ height: "100%" }}
>
<div
className={style.CardBack}
style={{ display: "flex", height: "100%" }}
>
<Card className={style.mainCard}>
<CardActions className={style.buttonFlipCard}>
<Tooltip title="Retour">
<IconButton
className={style.iconFlipCard}
disableRipple
onClick={() => setIsFlipped((prev) => !prev)}
>
<ChevronLeftIcon />
</IconButton>
</Tooltip>
</CardActions>
<CardContent>
<div className={style.divTagBack}>
<FormControl
sx={{width: "90%"}}
>
<InputLabel
id="SelectLabel"
sx={{display: "flex"}}
>
{<TagIcon />}
{" Tag"}
</InputLabel>
<Select
labelId="SelectLabel"
label={<TagIcon /> + " Tag"}
renderValue={(selected) => (
<Chip
onMouseDown={(event) => {
event.stopPropagation();
}}
key={selected}
label={selected}
icon={<TagIcon />}
/>
)}
defaultValue={item.dat_tag}
onChange={handleSelect}
>
{tags && tags.map((tag) => (
<MenuItem key={tag.dat_tag} value={tag.dat_tag}>
{tag.dat_tag}
</MenuItem>
))}
</Select>
</FormControl>
</div>
</CardContent>
</Card>
</div>
</ReactCardFlip>
</div>
</Grow>
);
};
When the user returns the card, he can change the title, description and tag of the chosen card.
My problem is with the Select.
In order to display the selected tag before any modification, I display the tag in defaultValue={item.dat_tag}
(Also tried with value and not defaultValue)
Then with my second prop, I build the list of my menu.
This is where I get my warning (which seems to extend the rendering time of the page considerably (Since it load / render like +100 definitions, getting a warning for every definition)
MUI: You have provided an out-of-range value Entreprise for the select component.
Consider providing a value that matches one of the available options or ''. The available values are "".
This is an example of what a console.logs told me about my props :
item.dat_tag
Entreprise
tags
0: {dat_tag: "Applicatif"}
1: {dat_tag: "Entreprise"}
2: {dat_tag: "Technique"}
3: {dat_tag: "Télécom"}
I already looked at several posts saying to put in a string variable my tag data (item.dat_tag) or to display my menu when it is not empty. No change.

Dialog of material UI has afterimage when being closed

Introduction
Bascially <Dialog /> receives open and onClose as props. open is boolean from state and onClose is a function that changes the state.
I made <CustomModal /> that wraps <Dialog />, which receives another prop content that defines what to display on <Dialog />.
// CustomModal.jsx
const CustomModal = props => (
<Dialog {...props} sx={{ '& .MuiDialog-paper': { padding: '2em' } }}>
{props.content}
</Dialog>
);
And I'm delivering handlers using context so that my modal could be open and closed everywhere.
// modalHandlersContext.js
const initialState = {
open: false,
content: null,
};
const ModalHandlersProvider = ({ children }) => {
const [modal, setModal] = useState(initialState);
const handlers = {
openModal: payload => setModal({ open: true, ...payload }),
closeModal: () => setModal(initialState),
};
return (
<ModalHandlersContext.Provider value={handlers}>
{children}
<CustomModal
open={modal.open}
onClose={handlers.closeModal}
content={modal.content}
></CustomModal>
</ModalHandlersContext.Provider>
);
};
When I want to open modal somewhere, I execute a function like this
const onLogin = () =>
openModal({
content: <h1>component</h1>,
});
That is, I pass a component. (I used h1 for a simple example)
Main subject
But when I close it, it's not clean.
I experimented on this to check when this happens.
It happens
With css, display something from props(same code as above)
const CustomModal = props => (
<Dialog {...props} sx={{ '& .MuiDialog-paper': { padding: '2em' } }}>
{props.content}
</Dialog>
);
It doesn't happen
Without css, display something from props
const CustomModal = props => (
<Dialog {...props}>
{props.content}
</Dialog>
);
2,3. With/Without css, display just plain text
const CustomModal = props => (
<Dialog {...props} sx={{ '& .MuiDialog-paper': { padding: '2em' } }}>
content
</Dialog>
);
const CustomModal = props => (
<Dialog {...props}>
content
</Dialog>
);
So after that, I tried using <DialogContent /> instead of css but It didn't work. But I have tried using <Modal /> instead of <Dialog /> and it hasn't caused any problems.
I wanna use <Dialog /> if possible but now can't find the cause.
Can anyone help?

list of ref shows undefined when logging

I've made a list of refs for each of my components that is being rendered in a map, I am assigning a ref to a button within EditWebAppTypeForm and am trying to use it within MappedAccordion but it shows undefined? what can I do to make sure ref is set before passing it in the MappedAccordion component?
The information logged in addtoRefs function is correct, it shows -
(2) [button, button]
I've removed a lot of the code so its easier to read.
function Admin() {
const allRefs = useRef([] as any);
allRefs.current = [];
const addtoRefs = (e: any) => {
if (e && !allRefs?.current?.includes(e)) {
allRefs?.current?.push(e);
}
console.log(allRefs.current); <-- Logs correct info
};
return (
<div className="adminContainer">
<Grid container spacing={2}>
<Grid item md={8} xs={12} sm={12}>
<div style={{ width: 725, paddingBottom: 150 }}>
{webAppTypes &&
webAppTypes.map((a: IWebAppType, index: number) => {
return (
<>
<Accordion
key={a.id}
defaultExpanded={a.id === 0 ? true : false}
>
<AccordionDetails>
<EditWebAppTypeForm
key={a.name}
setWebAppTypes={setWebAppTypes}
IWebAppTypeModel={a}
ref={addtoRefs} // <-- returning ref to add to list
/>
<MappedAccordion
waobj={a}
key={a.id}
setWebAppTypes={setWebAppTypes}
editRef={allRefs.current[index]} <-- using ref but showing undefined in MappedAccordion component
/>
</AccordionDetails>
</Accordion>
</>
);
})}
</div>
</Grid>
</Grid>
</div>
);
}
export default Admin;
EditWebAppTypeForm Component -
const EditWebAppTypeForm = (props: any, ref: any) => {
return (
<div className="editWebAppSContainer">
<form onSubmit={handleSubmit(onSubmit)} id="edit-app-form">
<button hidden={true} ref={ref} type="submit" /> // <-- Assiging ref to button
</form>
</div>
);
};
export default forwardRef(EditWebAppTypeForm);
type MappedAccordionProps = {
waobj: IWebAppType;
setWebAppTypes: Dispatch<SetStateAction<IWebAppType[]>>;
editRef: any;
};
function MappedAccordion({
waobj,
setWebAppTypes,
editRef,
}: MappedAccordionProps) {
const onSubmit = (data: FormFields) => {
console.log(editRef); // <-- Logs undefined
};
return (
<div>
<form onSubmit={handleSubmit(onSubmit)} id="environment-form">
</form>
</div>
);
}
export default MappedAccordion;
I would create an extra component WebAppTypeAccordion like this :
function WebAppTypeAccordion({a, setWebAppTypes}) {
const [formEl, setFormEl] = useState(null);
function handleRef(el) {
if (el) {
setFormEl(el)
}
}
return (
<Accordion defaultExpanded={a.id === 0}>
<AccordionDetails>
<EditWebAppTypeForm
setWebAppTypes={setWebAppTypes}
IWebAppTypeModel={a}
ref={handleRef}
/>
<MappedAccordion
waobj={a}
setWebAppTypes={setWebAppTypes}
editRef={formEl}
/>
</AccordionDetails>
</Accordion>
);
}
Then you can update the Admin component :
webAppTypes.map((a: IWebAppType) => (
<WebAppTypeAccordion key={a.id] a={a} setWebAppTypes={setWebAppTypes} />
))

How to map img src with the results of an async function in React?

I'm trying to use the map function to render images with Material UI, but I have to fetch the url from the API before displaying them, that is what getFoto() is doing, but It displays nothing
return(
<div className={classes.root}>
<GridList cellHeight={180} className={classes.gridList}>
<GridListTile key="Subheader" cols={2} style={{ height: 'auto' }}>
</GridListTile>
{data && data.map((tile) => (
<GridListTile key={tile.propertyId} >
<Link to={`/AreasRegistradas/${tile.propertyId}`}>
<img src={(async () => { // <======Here is the problem
await getFoto(tile.propertyId)
})()}
alt={tile.propertyName}
className={"MuiGridListTile-tile"}
/>
</Link>
<GridListTileBar
title={tile.propertyName}
subtitle={<span> {tile.address}</span>}
actionIcon={
<div>
<IconButton aria-label={`info about ${tile.title}`} className={classes.icon} onClick={()=>console.log("edit")}>
<EditIcon />
</IconButton>
<IconButton aria-label={`info about ${tile.title}`} className={classes.icon} onClick={()=>console.log("delete")}>
<DeleteForeverIcon />
</IconButton>
</div>
}
/>
</GridListTile>
))
}
</GridList>
</div>
)
However, if I do console.log (await getFoto(tile.propertyId)) it returns the correct urls that I need
//.....
<img src={(async () => {
console.log(await getFoto(tile.propertyId)) //this returns the values that I need in the console
})()}
//.....
What can be the problem here? I'm new in this async functions world please help.
Thanks!
Im using:
-"react": "^16.13.1"
When you set src={await getFoto(...)} you are setting the src attribute (a string obviously) to a Promise, which clearly won't work. Rather, somewhere in your component code, such as the componentDidMount event, you should fetch the image and set the result to some state variable which then becomes the src:
async componentDidMount() {
const photo = await getFoto(tile.propertyId);
this.setState({photo});
}
...
render() {
...
<img src={state.photo} />
But note, this is assuming that what is returned is photo URL. If it's the image itself, you'll need to use base64. Something like src={data:image/png;base64,${state.photo}}. It also assumes title is in scope in the componentDidMount method. If it isn't, you'll need to use the correct reference (e.g. this.tile, this.props.tile?).
Thanks to see sharper for the advice!!! Here's what I did:
First I created a new component called < Tile /> , added it inside my original map function and passed the item as props:
//...
{data && data.map((tile) => (
<div key={tile.propertyId}>
<Tile tile={tile} />
</div>
))
}
//...
Then inside my new < Tile /> component I added what I had originally inside my map function plus the async function inside a useEffect hook and store the fetched url in a useState hook:
function Tile(props){
const {tile} = props
const [imgSrc, setImgSrc] = useState(''); // here is the hook for the url
useEffect(() => {
const getFoto = async (propId) =>{
try{
const url = `....url/${propId}/images`
const response = await fetch(url, {
//authorization stuff
}
});
const responseData = await response.json()
setImgSrc(responseData.items[0].imageUrl) //setting the fetched url in a hook
}catch(error){
console.log(error)
}
}
getFoto(tile.propertyId);
}, []);
const useStyles = makeStyles((theme) => ({
root: {
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'space-around',
overflow: 'hidden',
backgroundColor: theme.palette.background.paper,
},
gridList: {
width: 500,
height: 450,
},
icon: {
color: 'rgba(255, 255, 255, 0.54)',
},
}));
const classes = useStyles();
return(
<GridListTile className={"MuiGridListTile-tile"}>
<Link to={`/AreasRegistradas/${tile.propertyId}`}>
<img src={imgSrc}
alt={tile.propertyName}
className={"MuiGridListTile-tile"}
/>
</Link>
<GridListTileBar
title={tile.propertyName}
subtitle={<span> {tile.address}</span>}
actionIcon={
<div>
<IconButton aria-label={`info about ${tile.title}`} className={classes.icon} onClick={()=>console.log("edit")}>
<EditIcon />
</IconButton>
<IconButton aria-label={`info about ${tile.title}`} className={classes.icon} onClick={()=>console.log("delete")}>
<DeleteForeverIcon />
</IconButton>
</div>
}
/>
</GridListTile>
)
}
Thanks again!

Expected an assignment or function call and instead saw an expression react router

I have button in a Material Table. I am using react routers to route pages to different URLs.
This page is supposed to set up functions and call the Material Table <MuiTable> and then render a button below the material table. It is set up this way due to the reusability of the MuiTable element.
export default function ListJobs(props) {
const url = 'http://localhost:8000/api/Jobs/'
const [data, loading] = DataLoader(url);
const handleEdit = (e,rowData) => {
<EditJob id={rowData.id} />
}
const handleDelete = (e,rowData) => {
//edit operation
<ListJobs />
DataDelete(url, rowData.id)
}
const createButton =
<div style={{display: 'flex', justifyContent:'center', alignItems:'center'}}>
<Button
component={Link} to='/Job/Create'
variant="contained"
color="primary">
Create New Job
</Button>
</div>
return (
<> {loading ? (
<Grid
container
spacing={0}
alignItems="center"
justify="center"
style={{ minHeight: '90vh' }}
>
<CircularProgress size="10vh" />
</Grid>
) : (
<MuiTable
model="Job"
data={data}
url={url}
handleEdit={handleEdit}
handleDelete={handleDelete}
createButton={createButton}
/>
)}
</>
);
}
This currently throws and error "Expected an assignment or function call and instead saw an expression" on the lines that call <EditJob...> and <ListJobs>. I know this is not the correct way to write this but, I want to change it to using react routers. I have my routers set up already but don't know how to use them in this instance. I want it to work something like this.
const handleEdit = (e,rowData) => {
<component ={Link} to='Jobs' />
}
I know this isn't correct eit,her because the react router link must be inside of a component like a<button>or <MenuItem>.
Try to return EditJob and ListJobs
const handleEdit = (e,rowData) => {
return <EditJob id={rowData.id} /> // return the function <EditJob />
}
const handleDelete = (e,rowData) => {
//edit operation
DataDelete(url, rowData.id) // Any operation before the return
return <ListJobs /> // return the function <ListJobs />
}

Categories

Resources