Invalid hook call. Hooks can only be called inside of the body of a function component. - useContext - javascript

I have created an empty object variable named workspacesData through the context api which is available globally across the app. I am trying to set my api data to the variable within the helper function that is below (getWorkspacesData).
This function getWorkspacesData is then being called on another page of the application for when the user wants to pull in the data and have it displayed on the page. Problem is when the user presses the button I get the error "Invalid hook call. Hooks can only be called inside of the body of a function component."
I know the issue is that I am using the useContext hook within this function, but how do I get around this issue? as I want to use the globally available variable to set the data to it.
getWorkspaces.js
import { useContext } from "react";
import { AppContext } from "../../../context/context";
import mavenlinkAPI from "../apiTools";
const GetWorkspaces = async (mavenlinkAccessToken) => {
const {setWorkspacesData} = useContext(AppContext);
try{
const data = await mavenlinkAPI(
'get',
'workspaces?token='+mavenlinkAccessToken,
);
console.log(data)
setWorkspacesData(data);
} catch (error) {
console.log(error)
}
}
export default GetWorkspaces;
MavenlinkPage.jsx
import { Container, Grid, Paper } from '#mui/material';
import React, {useContext, useEffect, useRef, useState} from 'react';
import { getAuth } from "firebase/auth";
import Fab from '#mui/material/Fab';
import AddIcon from '#mui/icons-material/Add';
import {SuccessSnackbar, ErrorSnackbar} from '../components/PopupSnackbar';
import { AppContext } from '../context/context';
import GetWorkspaces from '../helpers/api/mavenlink/getWorkspaces';
import GetTimesheets from '../helpers/api/mavenlink/getTimesheets';
import GetUsers from '../helpers/api/mavenlink/getUsers';
import CreateProject from '../helpers/api/mavenlink/createProject';
import GetMavenlinkAccessToken from '../helpers/api/mavenlink/getMavenlinkAccessToken';
import DummyDataHolder from '../components/dummyDataHolder';
// import { DataGrid } from '#material-ui/x-data-grid';
export const MavenlinkPage = () => {
const { mavenlinkConnected } = useContext(AppContext);
const [errorAlert, setErrorAlert] = useState(false);
const [successAlert, setSuccessAlert] = useState(false);
const { mavenlinkAccessToken, setMavenlinkAccessToken } = useContext(AppContext);
const { workspacesData } = useContext(AppContext);
const auth = getAuth();
const user = auth.currentUser;
const uid = user.uid
const handleAlertClose = (event, reason) => {
if (reason === 'clickaway') {
return;
}
setSuccessAlert(false) && setErrorAlert(false);
};
useEffect(() => {
if(mavenlinkConnected){
GetMavenlinkAccessToken(setMavenlinkAccessToken);
}
}, [])
console.log(mavenlinkAccessToken)
return(
<>
<Container>
<div className="mavenlink-page">
<Grid container spacing={2}>
<Grid item xs={12}>
<h1>Mavenlink</h1>
</Grid>
<Grid item xs={12}>
<Paper className="connection-status" elevation={1}>
<h4 className="title">Connection Status:</h4>
{!mavenlinkConnected ? <h4 className="response-error">{user.email} is not connected</h4> : <h4 className="response-success">{user.email} is connected</h4>}
</Paper>
</Grid>
<Grid item xs={12}>
<Paper elevation={1}>
<h4>Get Workspaces</h4>
<Fab onClick={() => GetWorkspaces(mavenlinkAccessToken)} color="primary" aria-label="add">
<AddIcon />
</Fab>
<h4>Get timesheets</h4>
<Fab onClick={() => GetTimesheets(mavenlinkAccessToken)} color="primary" aria-label="add">
<AddIcon />
</Fab>
<h4>Get users</h4>
<Fab onClick={() => GetUsers(mavenlinkAccessToken)} color="primary" aria-label="add">
<AddIcon />
</Fab>
<h4>Create project</h4>
<Fab onClick={() => CreateProject(mavenlinkAccessToken)} color="primary" aria-label="add">
<AddIcon />
</Fab>
</Paper>
</Grid>
<Grid item xs={12}>
<Paper elevation={10} style={{ height: 400, width: '100%' }}>
{workspacesData != null ?
Object.keys(workspacesData).map((item, index) => {
return(
<div key={index}>
{workspacesData[item].map((m, ind) =>
<div key={ind}>{item}</div>
)}
</div>
)
})
:
<div></div>
}
</Paper>
</Grid>
</Grid>
</div>
{successAlert === true ? <SuccessSnackbar open={successAlert} handleClose={handleAlertClose}/> : <></> }
{errorAlert === true ? <ErrorSnackbar open={errorAlert} handleClose={handleAlertClose}/> : <></> }
</Container>
</>
);
};

You can call the hook inside your component and pass the returned value from it to the function:
const getWorkspaces = async (mavenlinkAccessToken, onSuccess) => {
try{
const data = await mavenlinkAPI(
'get',
'workspaces?token='+mavenlinkAccessToken,
);
onSuccess?.(data)
} catch (error) {
console.log(error)
}
}
And call it as:
export const MavenlinkPage = () => {
const { setWorkspacesData } = useContext(AppContext);
...
onClick={() => getWorkspaces(mavenlinkAccessToken, setWorkspacesData )}
}
Or create a custom hook:
const useGetWorkspaces = (mavenlinkAccessToken) => {
const { setWorkspacesData } = useContext(AppContext);
return async () => {
try{
const data = await mavenlinkAPI(
'get',
'workspaces?token='+mavenlinkAccessToken,
);
setWorkspacesData(data);
} catch (error) {
console.log(error)
}
}
}
And use it like:
export const MavenlinkPage = () => {
const getWorkspaces = useGetWorkspaces(mavenlinkAccessToken);
...
onClick={() => getWorkspaces()}
}

Related

how do I prevent re-render in react

Now I have created this custom hook to perform lazy loading,which takes redux slice action as input and
import { useState, useEffect, useCallback, useRef } from "react";
import { useDispatch } from "react-redux";
function useLazyFetch(fetchAction) {
const dispatch = useDispatch();
const [page, setPage] = useState(1);
const [loading, setLoading] = useState(false);
const loadMoreRef = useRef(null);
const handleObserver = useCallback(async(entries) => {
const [target] = entries;
console.log(target.isIntersecting);
if (target.isIntersecting) {
console.log("INTERSECTING.....");
await new Promise((r) => setTimeout(r, 2000));
setPage((prev) => prev + 1);
}
}, []);
useEffect(() => {
const option = {
root: null,
rootMargin: "0px",
threshold: 1.0,
};
const observer = new IntersectionObserver(handleObserver, option);
if (loadMoreRef.current) observer.observe(loadMoreRef.current);
}, [handleObserver]);
const fetchApi = useCallback(async () => {
try {
setLoading(true);
await new Promise((r) => setTimeout(r, 2000));
dispatch(fetchAction(page))
setLoading(false);
} catch (err) {
console.error(err);
}
}, [page,fetchAction,dispatch]);
useEffect(() => {
fetchApi();
}, [fetchApi]);
return { loading, loadMoreRef };
}
export default useLazyFetch;
I am using this in my component like this, here you can see I am tracking div in the bottom using loadMoreRef from useLazyFetch, Now when I am commenting out the fetchApi(); from custom hook its working as expected, on scroll its logging INTERSECTING... in the console but the moment I try to execute the action through fetchApi() my whole app goes into loop,the div tracker with ref comes to top and it fetches the posts but after immediately that action repeats the tracker comes to top and page becomes empty & it fetches next set of posts,I can see that my list is getting appended new set of posts to state in redux dev tool instead of completely setting new state, but in UI it's rendering all posts again and again whic is causing the loop,how can I avoid this ?
import { CircularProgress, Grid, IconButton, Typography } from "#mui/material";
import { Box } from "#mui/system";
import React, { useEffect,useRef,useState } from "react";
import AssistantIcon from "#mui/icons-material/Assistant";
import Post from "../components/Post";
import { useDispatch, useSelector } from "react-redux";
import { getPosts } from "../redux/postSlice";
import AddPost from "../components/AddPost";
import useLazyFetch from "../hooks/useLazyFetch";
export default function Home() {
const dispatch = useDispatch();
// const api = `https://picsum.photos/v2/list`
const { status, posts } = useSelector((state) => state.post);
const {loading,loadMoreRef} = useLazyFetch(getPosts)
useEffect(() => {
dispatch(getPosts());
}, []);
return (
<Box>
<Box borderBottom="1px solid #ccc" padding="8px 20px">
<Grid container justifyContent="space-between" alignItems="center">
<Grid item>
<Typography variant="h6">Home</Typography>
</Grid>
<Grid item>
<IconButton>
<AssistantIcon />
</IconButton>
</Grid>
</Grid>
</Box>
<Box height="92vh" sx={{ overflowY: "scroll" }}>
<AddPost />
<Box textAlign="center" marginTop="1rem">
{status === "loading" && (
<CircularProgress size={20} color="primary" />
)}
</Box>
{status === "success" &&
posts?.map((post) => <Post key={post._id} post={post} />)}
<div style={{height:"50px",width:"100px",backgroundColor:"red"}} ref={loadMoreRef}>{loading && <p>loading...</p>}</div>
</Box>
</Box>
);
}
And here is my redux action & state update part
const initialState = {
status: "idle",
posts: []
};
export const getPosts = createAsyncThunk("post/getPosts", async (page) => {
console.log(page);
console.log("calling api ...");
const { data } = await axios.get(`/api/posts?page=${page}`);
return data;
});
export const postSlice = createSlice({
name: "post",
initialState,
reducers: {},
extraReducers: {
[getPosts.pending]: (state, action) => {
state.status = "loading";
},
[getPosts.fulfilled]: (state, action) => {
state.status = "success";
state.posts = [...state.posts,...action.payload.response.posts] ;
},
[getPosts.rejected]: (state, action) => {
state.status = "failed";
},
}
this is the solution that is working
import { CircularProgress, Grid, IconButton, Typography } from "#mui/material";
import { Box } from "#mui/system";
import React, { useEffect,useMemo } from "react";
import AssistantIcon from "#mui/icons-material/Assistant";
import Post from "../components/Post";
import { useDispatch, useSelector } from "react-redux";
import { getPosts } from "../redux/postSlice";
import AddPost from "../components/AddPost";
import useLazyFetch from "../hooks/useLazyFetch";
export default function Home() {
const { status, posts } = useSelector((state) => state.post);
const {loading,loadMoreRef} = useLazyFetch(getPosts)
const renderedPostList = useMemo(() => (
posts.map((post) => {
return( <Post key={post._id.toString()} post={post} />)
})
), [posts])
return (
<Box>
<Box borderBottom="1px solid #ccc" padding="8px 20px">
<Grid container justifyContent="space-between" alignItems="center">
<Grid item>
<Typography variant="h6">Home</Typography>
</Grid>
<Grid item>
<IconButton>
<AssistantIcon />
</IconButton>
</Grid>
</Grid>
</Box>
<Box height="92vh" sx={{ overflowY: "scroll" }}>
<AddPost />
<Box textAlign="center" marginTop="1rem">
{status === "loading" && (
<CircularProgress size={20} color="primary" />
)}
</Box>
{renderedPostList}
<div style={{height:"50px",width:"100px",backgroundColor:"red"}} ref={loadMoreRef}>{loading && <p>loading...</p>}</div>
</Box>
</Box>
);
}
}
I used useMemo hook to memoize and it works as expected

Search using queryParams displays the correct URL, but does not filter the components

In my application, I have a form that is supposed to search for the workout components that I have displayed based on two fields: category and difficulty. When I search, I can see that the URL changes properly, but the cards are not filtered at all. They disappear. Below I have the code for the component where the search is made:
import React, {useState, useEffect} from 'react'
import {Container, Grow, Grid, Paper, AppBar,TextField,Button, TextareaAutosize} from '#material-ui/core';
import {useDispatch} from 'react-redux';
import {getWorkouts, getWorkoutsBySearch} from '../../actions/workouts';
import {useNavigate, useLocation, Navigate} from 'react-router-dom';
import ChipInput from 'material-ui-chip-input'
import Workouts from "../Workouts/Workouts";
import Pagination from '../Pagination';
import Form from "../Form/Form";
import useStyles from './styles';
function useQuery(){
return new URLSearchParams(useLocation().search);
}
function Home() {
const [currentId, setCurrentId]=useState(null);
const classes=useStyles();
const dispatch=useDispatch();
const query=useQuery();
const history=useNavigate();
const page=query.get('page')||1;
const searchQuery=query.get('searchQuery');
const [search, setSearch] = useState('');
const [category, setCategory] = useState([]);
const searchWorkout=()=>{
if(search.trim() || category){
dispatch(getWorkoutsBySearch({search, category:category.join(',')}));
history(`/workouts/search?searchQuery=${search || 'none'} & category=${category.join(',')}`)
}else{
history(`/workouts`);
}
}
useEffect(()=>{
dispatch(getWorkouts());
},[currentId, dispatch]);
const handleKeyPress=(e)=>{
if(e.keyCode===13){
searchWorkout();
}
}
const handleAdd=(categ)=>setCategory([...category,categ]);
const handleDelete=(categoryToDelete)=>setCategory(category.filter((categ)=>categ!==categoryToDelete));
const user = JSON.parse(localStorage.getItem("profile"));
return (
<Grow in>
<Container maxWidth="xl">
<Grid className={classes.gridContainer} container justifyContent="space-between" alignItems='stretch' spacing={3}>
<Grid item xs={12} sm={6} md={9}>
<Workouts setCurrentId={setCurrentId}/>
</Grid>
<Grid item xs={12} sm={6} md={3}>
<AppBar className={classes.appBarSearch} position="static" color="inherit">
<TextField
name="search"
variant="outlined"
label="Search workouts"
onKeyPress={handleKeyPress}
fullWidth
value={search}
onChange={(e)=>setSearch(e.target.value)}
/>
<ChipInput
style={{margin:'10px 0'}}
value={category}
onAdd={handleAdd}
onDelete={handleDelete}
label="Search Category"
variant="outlined"
/>
<Button onClick={searchWorkout} className={classes.searchButton} variant="contained" color="primary">Search</Button>
</AppBar>
{
user?.result?.email.endsWith('admin.com')?
<>
<Form currentId={currentId} setCurrentId={setCurrentId}/>
<Paper className={classes.pagination} elevation={6}>
<Pagination page={page}/>
</Paper>
</>
:<></>
}
</Grid>
</Grid>
</Container>
</Grow>
)
}
export default Home
The backend request is the following one:
export const getWorkoutsBySearch = async (req, res) => {
const { searchQuery, category } = req.query;
try {
const difficulty = new RegExp(searchQuery, "i");
const workouts = await WorkoutMessage.find({ $or: [ { category }, { difficulty } ]});
res.json({ data: workouts });
console.log(workouts);
} catch (error) {
res.status(404).json({ message: error.message });
}
}
The search in the actions folder is this one:
export const getWorkouts=()=> async (dispatch)=>{
try{
const data=await api.fetchWorkouts();
dispatch({type:FETCH_ALL,payload:data});
}catch(error){
console.log(error);
}
}
export const getWorkoutsBySearch=(searchQuery)=>async(dispatch)=>{
try{
const {data: {data}}=await api.fetchWorkoutsBySearch(searchQuery);
dispatch({type:FETCH_BY_SEARCH,payload:data});
}catch(error){
console.log(error);
}
}
The reducer is below:
case FETCH_BY_SEARCH:
return action.payload;
Could you help me solve this?

Strange State / UseEffect Behaviour using NextJS Link

I am experiencing so really strange behaviour in this personal project i am doing. In short it is a recipe website, with buttons at the top that direct to different pages that ultimately query firebase and pull down a query.
Index.js File - Queries Firestore passes props to FoodCard.js to Render a list of all recipes
breakfast.js - Queries Firestore with a filter and passes same props down (with different results) to FoodCard.js
Behaviour
When i click on Breakfast JS, it brings up my list of filtered results correctly, however when i click my Next Link "Welcome to the Family Heirloom" to return to the index the first click doesnt respond, then the second click returns home, but with the filtered breakfast result concatonated with all the original results (effectively causing duplicates)Index on First Render
Breakfast filter successful
index with the now duplicate pancake result
I have messed about wondering if useEffect is not getting triggered which you may see in the code, but that didnt seem to work, so at a loss
Index.js
import { React, useEffect, useState } from 'react'
import { useRouter } from 'next/dist/client/router'
import { useTheme } from '#mui/material/styles'
import { makeStyles } from '#mui/styles'
import Modal from '#mui/material/Modal'
import FoodCard from '../src/ui/FoodCard.js'
import firebase from '../firebase/initFirebase'
import Box from '#mui/material/Box'
import Typography from '#mui/material/Typography'
const useStyles = makeStyles(theme => ({
mainContainer: {
}
}))
const style = {
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 400,
bgcolor: 'red',
border: '2px solid #000',
boxShadow: 24,
p: 4,
};
export default function CookBook() {
const router = useRouter();
const { id } = router.query;
const classes = useStyles()
const theme = useTheme()
const [loading, setLoading] = useState(true);
const [recipes, setRecipes] = useState([]);
const [open, setOpen] = useState(false);
const handleOpen = () => setOpen(true);
const handleClose = () => setOpen(false);
useEffect(() => {
const getRecipesFromFirebase = [];
const subscriber = firebase.firestore()
.collection("recipes")
.onSnapshot((querySnapshot) => {
querySnapshot.forEach((doc) => {
console.log(querySnapshot)
getRecipesFromFirebase.push({
...doc.data(), //spread operator
key: doc.id, // `id` given to us by Firebase
});
});
setRecipes(getRecipesFromFirebase);
console.log(recipes);
setLoading(false);
});
return () => subscriber();
}, [loading, router.events]); // empty dependencies array => useEffect only called once
if (loading) {
return (
<Modal
open={open}
onClose={handleClose}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Box sx={style}>
<Typography id="modal-modal-title" variant="h6" component="h2">
Loading Data
</Typography>
</Box>
</Modal>)
}
return (
<FoodCard recipes={recipes} />
)
}
_app.js
import * as React from 'react';
import PropTypes from 'prop-types';
import Head from 'next/head';
import { ThemeProvider } from '#mui/material/styles';
import CssBaseline from '#mui/material/CssBaseline';
import { CacheProvider } from '#emotion/react';
import theme from '../src//ui/theme';
import createEmotionCache from '../src/createEmotionCache';
import Header from '../src/ui/Header';
import Grid from '#mui/material/Grid'
import Typography from '#mui/material/Typography'
import { Link as MUILink } from '#mui/material/'
import NextLink from 'next/link'
// Client-side cache, shared for the whole session of the user in the browser.
const clientSideEmotionCache = createEmotionCache();
export default function MyApp(props) {
const { Component, emotionCache = clientSideEmotionCache, pageProps } = props;
return (
<CacheProvider value={emotionCache}>
<Head>
<meta name="viewport" content="initial-scale=1, width=device-width" />
</Head>
<ThemeProvider theme={theme}>
<Header />
<Grid container justify="center" alignItems="center" direction="column" >
<Grid item>
<NextLink href="/" passHref>
<MUILink underline="none" color="secondary" variant="h1">
Welcome To the Family Heirloom
</MUILink>
</NextLink>
</Grid>
</Grid>
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
<CssBaseline />
<Component {...pageProps} />
</ThemeProvider>
</CacheProvider>
);
}
MyApp.propTypes = {
Component: PropTypes.elementType.isRequired,
emotionCache: PropTypes.object,
pageProps: PropTypes.object.isRequired,
};
Breakfast.js
import { React, useEffect, useState } from 'react'
import FilterMains from '../../src/ui/FilterMains'
import { useRouter } from 'next/dist/client/router'
import FoodCard from '../../src/ui/FoodCard'
import {
getFirestore, collection, query, where, onSnapshot
} from 'firebase/firestore'
const Breakfast = () => {
const router = useRouter();
const { id } = router.query;
const [breakfastloading, setBreakfastLoading] = useState(true);
const [breakfastRecipe, setBreakfastRecipe] = useState([]);
const db = getFirestore()
const docRef = collection(db, 'recipes')
//Query
const q = query(docRef, where("category", "==", 'Breakfast'))
useEffect(() => {
const getBreakfastFromFirebase = [];
onSnapshot(q, (snapshot) => {
snapshot.docs.forEach((doc) => {
getBreakfastFromFirebase.push({ ...doc.data() })
})
setBreakfastRecipe(getBreakfastFromFirebase)
setBreakfastLoading(false)
console.log(breakfastRecipe)
})
}, [breakfastloading, router.events]);
if (breakfastloading) {
return (
<h2>Loading Data</h2>
)
}
return (
<FoodCard recipes={breakfastRecipe} />
// <FoodCard recipes={recipes} />
)
}
export default Breakfast
FoodCard.js
import React from 'react'
import Card from '#mui/material/Card'
import CardHeader from '#mui/material/CardHeader';
import CardMedia from '#mui/material/CardMedia';
import Grid from '#mui/material/Grid'
import Container from '#mui/material/Container';
import Link from 'next/link'
import CardActionArea from '#mui/material/CardActionArea';
function FoodCard(props) {
return (
<div>
<Container>
< Grid container justify="center" alignItems="center" direction="row" >
<Grid container spacing={2}>
{props.recipes.map((recipe) => (
<Link href={`/recipes/${recipe.key}`} passHref>
<Grid key={recipe.id} item xs={12} md={6}>
<Card elevation={3} sx={{ maxWidth: 400 }}>
<CardActionArea>
<CardHeader
titleTypographyProps={{ fontWeight: "Bold" }}
title={recipe.title}
subheader={recipe.description}
/>
<CardMedia
component="img"
height="194"
image="/assets/comingsoon.jpg"
alt="Mac and Cheese"
/>
</CardActionArea>
</Card>
</Grid>
</Link>
))}
</Grid>
</Grid >
</Container>
</div>
)
}
export default FoodCard
Cracked it, although i can't fully explain why. In my learning i used two different methods for interfacing with firebase. The first method on the index page was what i would call the older way. The second method i used was in all of my other files, specific to interfacing with Version 9 of Firebase.
from this:
const subscriber = firebase.firestore()
.collection("recipes")
.onSnapshot((querySnapshot) => {
querySnapshot.forEach((doc) => {
console.log(querySnapshot)
getRecipesFromFirebase.push({
...doc.data(), //spread operator
key: doc.id, // `id` given to us by Firebase
});
});
to this:
let getRecipesFromFirebase = [];
getDocs(colRef)
.then((snapshot) => {
let getRecipesFromFirebase = [];
snapshot.docs.forEach((doc) => {
getRecipesFromFirebase.push({ ...doc.data(), key: doc.id })
})
setRecipes(getRecipesFromFirebase);
console.log(recipes);
setLoading(false);
})
.catch(error => {
console.log(error.message)
})
Would love somebody more knowledgable (3 months into React) than myself to tell me as to why though.

Calling child component function

So im trying to call a function from child component, but all I do ends up with an error. I've been searching for more than hour and found many solutions, but most of them are either outdated or I'm doing something wrong.
Here's my parent component and theres simple getData function in child component named Query.
const ref = useRef();
return(
<Query ref={ref} />
<button onClick={() => ref.current.getData()}>dasfas</button>
)
I also tried forwardRef hook, but I couldn't call it anyway.
Move the function to the parent component and pass data to Query component as props.
import React from "react";
import { Grid, Typography } from "#material-ui/core";
const Query = ({ query }) => {
return (
<Grid container item sm={12}>
<Grid item sm={12}>
<Typography variant="h5">Queries</Typography>
</Grid>
<Grid item sm={12}>
<div id="sqlQuery" />
</Grid>
<Grid item sm={12}>
<div id="treeViewBefore" />
</Grid>
<Grid item sm={12}>
<div id="treeViewAfter" />
</Grid>
</Grid>
);
};
export default Query;
Parent component
import React, { useState } from "react";
const Parent = () => {
const [sqlQuery, setSqlQuery] = useState("");
const getData = async () => {
if (global.answercreated) {
var response = await fetch(
`${global.serverURL}?type=before&&property=${global.token}`
);
var data = await response.text();
const jsonbefore = JSON.parse(data.replace(/'/g, '"'));
response = await fetch(
`${global.serverURL}?type=after&&property=${global.token}`
);
data = await response.text();
const jsonafter = JSON.parse(data.replace(/'/g, '"'));
response = await fetch(
`${global.serverURL}?type=sqlquery&&property=${global.token}`
);
data = await response.text();
const sqlQuery = data;
}
};
return (
<div>
<Query query={sqlQuery} />
<button onClick={() => getData()}>dasfas</button>
</div>
);
};
export default Parent;

How are parameters accessed by arrow function if they are never passed in?

The PhotosPage component below is rendered using
<Route path="/settings/photos" component={PhotosPage} />
The signature of the component is:
const PhotosPage = ({
uploadProfileImage,
photos,
profile,
deletePhoto,
setMainPhoto
})=>{}
However, there are two types of parameters that are used:
photos and profile are part of redux state
uploadProfileImage, deletePhoto, and setMainPhoto are imports
That are never passed in. Are these parameters passed in by react, or is this a javascript feature that I don't understand. Thanks.
import React, { useState, useEffect, Fragment } from "react";
import { connect } from "react-redux";
import { compose } from "redux";
import { firestoreConnect } from "react-redux-firebase";
import {
Segment,
Header,
Divider,
Grid,
Button
} from "semantic-ui-react";
import { toastr } from "react-redux-toastr";
import DropzoneInput from "./DropzoneInput";
import CropperInput from "./CropperInput";
import {
uploadProfileImage,
deletePhoto,
setMainPhoto
} from "../../userActions";
import UserPhotos from "./UserPhotos";
const query = ({ auth }) => {
return [
{
collection: "users",
doc: auth.uid,
subcollections: [{ collection: "photos" }],
storeAs: "photos"
}
];
};
const actions = {
uploadProfileImage,
deletePhoto
};
const mapState = state => ({
auth: state.firebase.auth,
profile: state.firebase.profile,
photos: state.firestore.ordered.photos
});
const PhotosPage = ({
uploadProfileImage,
photos,
profile,
deletePhoto,
setMainPhoto
}) => {
const [files, setFiles] = useState([]);
const [image, setImage] = useState(null);
useEffect(() => {
return () => {
files.forEach(file => URL.revokeObjectURL(file.preview));
};
}, [files]);
const handleUploadImage = async () => {
try {
await uploadProfileImage(image, files[0].name);
handleCancelCrop();
toastr.success("Success", "photo has been uploaded.");
} catch (error) {
toastr.error("Ooops", "something whent wrong");
console.log(error);
}
};
const handleCancelCrop = () => {
setFiles([]);
setImage(null);
};
const handleDeletePhoto = async photo => {
//try {
await deletePhoto(photo);
// } catch (error) {
// toastr.error("OOps", error.message);
// }
};
return (
<Segment>
<Header dividing size="large" content="Your Photos" />
<Grid>
<Grid.Row />
<Grid.Column width={4}>
<Header color="teal" sub content="Step 1 - Add Photo" />
<DropzoneInput setFiles={setFiles} />
</Grid.Column>
<Grid.Column width={1} />
<Grid.Column width={4}>
<Header sub color="teal" content="Step 2 - Resize image" />
{files.length > 0 && (
<CropperInput setImage={setImage} imagePreview={files[0].preview} />
)}
</Grid.Column>
<Grid.Column width={1} />
<Grid.Column width={4}>
<Header sub color="teal" content="Step 3 - Preview & Upload" />
{files.length > 0 && (
<>
<div
className="img-preview"
style={{
minHeight: "200px",
minWidth: "200px",
overflow: "hidden"
}}
/>
<Button.Group>
<Button
onClick={handleUploadImage}
style={{ width: "100px" }}
positive
icon="check"
/>
<Button
onClick={handleCancelCrop}
style={{ width: "100px" }}
icon="close"
/>
</Button.Group>
</>
)}
</Grid.Column>
</Grid>
<Divider />
<UserPhotos
photos={photos}
profile={profile}
deletePhoto={handleDeletePhoto}
/>
</Segment>
);
};
export default compose(
connect(
mapState,
actions
),
firestoreConnect(auth => query(auth))
)(PhotosPage);
When we wrap a component with 'Connect' this is then connected to our Redux store. We can then give it 2 functions - mapStateToProps (which maps our store state to the component props) and mapDispatchToProps - which we call actions - (that maps our store actions to our component). The actions become part of the props that we can call from the component.

Categories