calling method on sub component in React functional component via ref - javascript

I'm using syncfusion react controls to add some functionality to my app. I want to call a method on the control in my functional component, but I haven't been able to get the ref set properly:
import React, {createRef, useEffect, useState} from "react";
import {AutoCompleteComponent} from "#syncfusion/ej2-react-dropdowns";
import "#syncfusion/ej2-base/styles/bootstrap.css";
import "#syncfusion/ej2-react-inputs/styles/bootstrap.css";
import "#syncfusion/ej2-react-dropdowns/styles/bootstrap.css";
const UserLookup = ({userSelected}) => {
const [searchString, setSearchString] = useState('');
const [items, setItems] = useState([]);
const helper = new QueryHelper();
let listObj = createRef();
const searchStringChanged = (args) => {
console.log(args.text);
if (args.text.length > 3) {
setSearchString(args.text);
}
}
const optionSelected = (event) => {
memberSelected(event.item.id);
}
useEffect(() => {
fetch('http://example.com/myendpoint')
.then(response.map((result) => {
listObj.current.showPopup(); // <-- this method should be called on the autocomplete component
return {
id: result.contactId,
label: result.firstName + ' ' + result.lastName
}
}))
.then(data => console.log(data));
}, [searchString]);
return (
<AutoCompleteComponent
id="user_search"
autofill={true}
dataSource={items}
fields={
{
value: 'label'
}
}
filtering={searchStringChanged}
select={optionSelected}
popupHeight="250px"
popupWidth="300px"
placeholder="Find a contact (optional)"
ref={listObj}
/>
);
};
export default UserLookup;
this always throws an error that Cannot read property 'showPopup' of null -- how do you set the ref for the instance of the AutoCompleteComponent so that you can call methods on it?

We can get the reference for the AutoComplete when it's rendered as a functional component with help of using useRef method instead of createRef method. Please find the modified sample from below.
Sample Link: https://codesandbox.io/s/throbbing-shadow-ddsmf

Related

The object passed as the value prop to the Context provider changes every render. How to Fix it with useMemo?

I have the following problem,
The object passed as the value prop to the Context provider (at line 20) changes every render. To fix this consider wrapping it in a useMemo hook.
I don't know hot to use useMemo in this case. So how do I fix it?
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import Context from './Context';
function Provider({ children }) {
const [data, setData] = useState([]);
useEffect(() => {
const getDataAPI = async () => {
const response = await fetch('https://swapi-trybe.herokuapp.com/api/planets/');
const dataAPI = await response.json();
const alteredData = dataAPI.results.map(({ residents, ...params }) => params);
const result = [...alteredData];
setData(result.sort((a, b) => a.name.localeCompare(b.name)));
};
getDataAPI();
}, []);
return (
<Context.Provider value={ { data } }>
{ data[0] && children }
</Context.Provider>
);
}
Provider.propTypes = {
children: PropTypes.node.isRequired,
};
export default Provider;
Look at what you are passing:
value={ { data } }
It is an object {data : data}. This is defined everytime.
You better pass value={data} and change your useContext hooks accordingly in children

How to use Hooks inside useEffect?

I wrote a demo here:
import React, { useRef, useEffect, useState } from "react";
import "./style.css";
export default function App() {
// let arrRef = [useRef(), useRef()];
let _data = [
{
title: A,
ref: null
},
{
title: B,
ref: null
}
];
const [data, setData] = useState(null);
useEffect(() => {
getDataFromServer();
}, []);
const getDataFromServer = () => {
//assume we get data from server
let dataFromServer = _data;
dataFromServer.forEach((e, i) => {
e.ref = useRef(null)
});
};
return (
<div>
{
//will trigger some function in child component by ref
data.map((e)=>(<div title={e.title} ref={e.ref}/>))
}
</div>
);
}
I need to preprocess after I got some data from server, to give them a ref property. the error says 'Hooks can only be called inside of the body of a function component' . so I checked the document, it says I can't use hooks inside a handle or useEffect. so is there a way to achieve what I need?
update:
I need to create component base on DB data, so when I create a component I need to give them a ref , I need trigger some function written in child component from their parent component and I use ref to achieve that. that is why I need to pass a ref to child component.

React: How to use one variable in one component when it is imported to another component

import React, { useEffect, useState } from 'react';
import { Card } from 'components/Card';
import { dateFilter } from 'helpers';
import Chart from 'chart.js';
import 'chartjs-chart-matrix';
import chroma from 'chroma-js';
import moment from 'moment';
const WeeklyTrafficCard = (props) => {
const { start, end, data, store } = props;
const capacity = store && store.capacity;
var numberOfweeks = 0; //representing how many weeks back
const dateArray = [];
var today = moment();
while (numberOfweeks < 10) {
var from_date = today.startOf('week').format('MM/DD/YY');
var to_date = today.endOf('week').format('MM/DD/YY');
var range = from_date.concat(' ','-',' ',to_date);
dateArray.push(range);
today = today.subtract(7, 'days');
numberOfweeks++;
//console.log(dateArray);
}
const [each_daterange, setDateRange] = useState();
I have this Component called WeeklyTrafficCard and I want to use the variable, each_daterange, in another component, which imported WeeklyTrafficCard as below to send the get request, clearly I cannot use each_daterange directly right here, how I can work around it?
import React, { useContext, useEffect, useState } from 'react';
import { WeeklyTrafficCard } from './WeeklyTrafficCard';
import { AppContext } from 'contexts/App';
import { API_URL } from 'constants/index.js';
import { todayOpen, todayClose } from 'helpers';
import moment from 'moment';
const WeeklyTrafficCardContainer = (props) => {
const { API } = useContext(AppContext);
const { store = {} } = props;
const [data, setData] = useState([]);
const open = todayOpen(store.hours, store.timezone);
const close = todayClose(store.hours, store.timezone);
useEffect(() => {
async function fetchData() {
const result = await API.get(`${API_URL}/api/aggregates`, {
params: {
each_daterange,
every: '1h',
hourStart: 13,
hourStop: 4
},
});
You should use a useEffect(prop drilling) to pass your variable in your parent:
import React, { Component } from "react";
import { render } from "react-dom";
import "./style.css";
const App = () => {
const [myVar, setMyVar] = React.useState('');
return (
<div>
<Child setMyVar={setMyVar} />
{myVar}
</div>
);
};
const Child = ({setMyVar}) => {
const myChildVar = "Hello world !"
React.useEffect( () => setMyVar(myChildVar),[]);
return <div> This is the child</div>
}
render(<App />, document.getElementById("root"));
Here is the repro on stackblitz
Understanding of the Problem
You want to pass data up to the parent from the child.
Manage each_daterange in the parent:
Instead of creating your useState variable each_daterange in the child you can declare it in the parent and pass down it's setter function. For instance:
const WeeklyTrafficCardContainer = (props) => {
const [eachDateRange, setEachDateRange] = useState();
return (
<div>
{/* your return */}
<WeeklyTrafficCard setEachDateRange={setEachDateRange} />
</div>
)
}
If you need to display eachDateRange in the traffic card, or the traffic card needs to completely own that variable, you can create another state variable in the parent and pass a callback to the child (essentially what is above but now you have two different state variables).
The parent becomes
const WeeklyTrafficCardContainer = (props) => {
const [requestDateRange, setRequestDateRange] = useState();
const updateRequestDateRange = (dateRange) => {
setRequestDateRange(dateRange)
}
return (
<div>
{/* your return */}
<WeeklyTrafficCard updateDateRange={updateRequestDateRange} />
</div>
)
}
Then in your WeeklyTrafficCard call props.updateDateRange and pass it the date range whenever each_daterange changes.
Ciao, of course you need a global state manager. My preferred is react-redux. In few word, react-redux allows you to have a state that is shared in all your components. Sharing each_daterange between WeeklyTrafficCardContainer and WeeklyTrafficCard will be very easy if you decide to use it.
This is the more appropriate guide to quick start with react-redux. have a nice coding :)
Keep the value outside of the component, where both can access it. There are other ways to do this, but just as a simple example you could create a simple "store" to hold it and reference that store from each component that needs it:
class Store {
setDateRange (newDateRange) {
this._dateRange = newDateRange;
}
get dateRange () {
return this._dateRange;
}
}
export default new Store(); // singleton; everyone gets the same instance
import store from './Store';
const WeeklyTrafficCard = (props) => {
// use current dateRange value
const dateRange = store.dateRange;
// set new dateRange
store.setDateRange( newDateRange );
// do other stuff
}
import store from './Store';
const WeeklyTrafficCardContainer = (props) => {
// use current dateRange value
const dateRange = store.dateRange;
// set new dateRange
store.setDateRange( newDateRange );
// do other stuff
}
If you want store updates to trigger component re-renders you'd need to add some higher order component plumbing, like redux's connect, or some other mechanism for triggering updates:
// pseudocode; make store an event emitter and return
// a component that re-renders on store events
store.connect = Component => {
return props => {
React.useEffect(() => {
store.addEventListener( ... )
return () => store.removeEventListener( ... )
})
}
}
Or if the components share a common parent, you could lift the state to the parent and pass the information to each component as props. If either component updates the value, the parent state change will trigger a re-render of both components with the new value:
const Parent = () => {
const [dateRange, setDateRange] = React.useState();
return (
<>
<WeeklyTrafficCardContainer
dateRange={dateRange}
onDateRangeChange={newRange => setDateRange(newRange)}
/>
<WeeklyTrafficCard
dateRange={dateRange}
onDateRangeChange={newRange => setDateRange(newRange)}
/>
</>
);
}
Let's rephrase the objective here.
Objective: access each_daterange from WeeklyTrafficCard component in WeeklyTrafficCardContainer component.
Note: simply put, choose the following case based on your problem.
choose using prop if the variable is to be accessed by only one component
choose using context if the variable is to be accessed by more than one components
Solution Cases:
Case A: using prop.
Case A.1. WeeklyTrafficCard is the parent of WeeklyTrafficCardContainer
each_datarange being passed from WeeklyTrafficCard component as prop to WeeklyTrafficCardContainer component
working example for reference: codesandbox - variable passed as prop
// WeeklyTrafficCard.jsx file
const WeeklyTrafficCard = () => {
const [each_daterange, setDateRange] = useState();
return (
<>
...
<WeeklyTrafficCardContainer eachDateRange={each_daterange} />
</>
);
};
// WeeklyTrafficCardContainer.jsx file
const WeeklyTrafficCardContainer = props => {
const eachDateRange = props.eachDateRange;
return (
<>
...
</>
);
};
Case A.2. WeeklyTrafficCard & WeeklyTrafficCardContainer are children of a parent, say WeeklyTraffic component
each_datarange will be present in WeeklyTraffic component which is shared among WeeklyTrafficCard component & WeeklyTrafficCardContainer component
// WeeklyTraffic.jsx file
const WeeklyTraffic = () => {
const [each_daterange, setDateRange] = useState();
return (
<>
...
<WeeklyTrafficCard eachDateRange={each_daterange} />
<WeeklyTrafficCardContainer eachDateRange={each_daterange} />
</>
);
};
// WeeklyTrafficCard.jsx file
const WeeklyTrafficCard = props => {
const eachDateRange = props.eachDateRange;
return (
<>
...
</>
);
};
// WeeklyTrafficCardContainer.jsx file
const WeeklyTrafficCardContainer = props => {
const eachDateRange = props.eachDateRange;
return (
<>
...
</>
);
};
Case B: using context.
follow blog example found: blog - react context
this is preferred way to implement if the variable/variables is/are shared or need to be accessed by more than 1 components

React hooks component callback only has initial state

I am new to react hooks, however I have a problem that I would think is fairly straight forward. Here is my parent component:
import React, { useState, useEffect } from 'react';
import DragAndDrop from '../DragAndDrop';
import Attachment from './Attachment';
import API from '../../services/api';
import '../../styles/components/attachments.scss';
const api = API.create();
const Attachments = ({attachments, type, typeId}) => {
const [attachmentData, setAttachmentData] = useState([]);
useEffect(() => {
setAttachmentData(attachments);
}, [attachments])
function onUpload(files) {
if (typeId) {
api.AddAttachment(type, typeId, files).then(response => {
let newAttachments = response.data.data;
let newAttachmentData = attachmentData;
newAttachmentData = newAttachmentData.concat(newAttachments);
setAttachmentData(newAttachmentData);
});
}
}
return (
<div className="attachments">
<h3 className="attachments-title">Attachments</h3>
<DragAndDrop onUpload={onUpload} />
{attachmentData.map((attachment, index) => (
<Attachment key={index} attachment={attachment} />
))}
</div>
);
}
export default Attachments;
attachments is passed in from the parent component async, which is why I'm using the useEffect function.
This all works fine, and the child Attachment components are rendered when the data is received.
I have a callback onUpload which is called from DragAndDrop component:
import React, { useCallback } from 'react';
import {useDropzone} from 'react-dropzone';
import '../styles/components/dragAndDrop.scss';
const DragAndDrop = ({onUpload}) => {
const onDrop = useCallback(acceptedFiles => {
onUpload(acceptedFiles);
}, [])
const {getRootProps, getInputProps} = useDropzone({onDrop});
return (
<div>
<div {...getRootProps({className: 'dropzone'})}>
<input {...getInputProps()} />
<p>Drag 'n' drop some files here, or click to select files</p>
</div>
</div>
);
};
export default DragAndDrop;
My problem is, when the callback onUpload in the Attachments component is called, attachmentData is the initial value which is an empty array instead of the being populated with the attachments. In my onUpload function, I'm posting the new uploads to my API which then returns them in the same format as the rest of the attachments. I then want to concat these new attachments to the attachmentData. I need attachmentData to have it's filled in value within the callback. Why is the attachmentData the initial state []? How do I get this to work?
The problem is that you're accessing attachmentData in onUpload which becomes stale by the time you use it, so to get the latest attachmentData you can pass a callback function to you updater function setAttachmentData like this:
function onUpload(files) {
if (typeId) {
api.AddAttachment(type, typeId, files).then(response => {
let newAttachments = response.data.data;
setAttachmentData(prevAttachmentData => ([...prevAttachmentData, ...newAttachments]));
});
}
}
If you want to access the attachmentsData inside onUpload, you can do so by creating a ref and then updating that ref whenever attachmentsData changes, that way you won't have to pass a function to setAttachmentsData also:
const [attachmentsData, setAttachmentsData] = React.useState([]);
const attachmentsDataRef = React.useRef(attachmentsData);
// Update ref whenever state changes
useEffect(() => {
attachmentsDataRef.current = attachmentsData;
}, [attachmentsData]);
// Now in onUpload
function onUpload(files) {
// Here you can access attachmentsDataRef.current and you'll get updated state everytime
if (typeId) {
api.AddAttachment(type, typeId, files).then(response => {
let newAttachments = response.data.data;
setAttachmentData([...attachmentsDataRef.current, ...newAttachments]);
});
}
}

React: TypeError: _useContext is undefined

I'm trying my hand at TypeScript and React. I have a functional component (code below) that is supposed to consume a context with useContext, but it is showing me this weird error that I cannot find a solution to.
If I do not use TS, and go with JSX, it works just fine.
Edit: Screenshot>
Code:
AppProvider.tsx
import React, { useState, useEffect } from "react";
// Application's context (for general application-wide usage)
const AppContext: any = React.createContext(null);
// this will be used below in the componet we will export
export const AppContextProvider = AppContext.Provider;
export const AppProvider: React.FC = (props: any) => {
const [appName, setAppName] = useState("Blood Donation");
const [appUser, setAppUser]: any = useState(null);
const [appInfoBusy, setAppInfoBusy] = useState(false); // working to get or set data
useEffect(() => {
getAppInfo();
}, []);
const getAppInfo = () => {
setTimeout(() => {
setAppName("Test");
setAppUser({
name: "Admin",
email: "test#test.com",
role_id: 100
});
}, 3000);
};
return (
<AppContextProvider
value={{
appName: appName,
appInfoBusy: appInfoBusy,
appUser: appUser
}}
>
{props.children}
</AppContextProvider>
);
};
Consumer: Login.tsx
import React, { useState, useEffect, useContext } from "react";
import {
Button,
Card,
Elevation,
FormGroup,
InputGroup,
Drawer,
Classes,
H4,
Callout,
H5
} from "#blueprintjs/core";
//#ts-ignore
import ReCAPTCHA from "react-google-recaptcha";
import logo from "../../assets/images/logo.png";
import "../../scss/Login.scss";
import { RecaptchaKey } from "../../shared/Info";
import { AppContextProvider } from "../../shared/context/AppProvider";
const Login: React.FC = props => {
const [email, setEmail]: React.ComponentState = useState();
const [password, setPassword]: any = useState();
const [isOpen, setIsOpen]: any = useState();
const [resetEmail, setResetEmail]: any = useState();
const [emailSent, setEmailSent]: any = useState();
const [captchaOk, setCaptchaOk]: any = useState(false);
const [working, setWorking]: any = useState(false);
// context
const { appName, appUser, appInfoBusy } = useContext(AppContextProvider);
/**
* Handles lifecycle hooks
*/
useEffect(() => {
// when component is mounted
}, []);
/**
* Handles Captcha change
* #param value
*/
const recaptchaChange = (value: any) => {
setCaptchaOk(value ? true : false);
};
const handleRecoverySubmit = () => {
setWorking(true);
setTimeout(() => {
setEmailSent(true);
setWorking(false);
}, 3000);
};
return (
<div id="loginPage">
... removed for brevity ...
</div>
);
};
export default Login;
Any help is gratefully thanked. React and dependencies are all latest as of date.
I was using the context provider instead of the context itself inside useContext(), I should have used useContext(AppContext) instead.
Commentary removed because stackoverflow.
The error is _useContext not defined. The issue is different than what it is actually referring to.
you created a context called as AppContext
and then you export this as
export const AppContextProvider = AppContext.Provider;
You have done correct till this stage.
The problem lies at consumer part i.e. login.tsx file.
you are importing a name file inside a curly braces which is not correct, because the context is exported as a name variable. You simply need to write
import AppContextProvider from "../../shared/context/AppProvider";
That's it, and when you are calling this context using useContext hooks, then the actual state that you are looking for get accessed and no issue will further persist.
Note: Don't use {} for importing named exports.
reference: When should I use curly braces for ES6 import?

Categories