Kendo React PDF and changing from class component to function component - reactjs

I'm changing my class component to a function component and stuck on the render PDF. It was working fine in the class component. The Kendo docs only have a class component usage guide.
This component simply renders HTML and gives the user the option to download a PDF.
import React, { Fragment } from 'react';
import { firestoreConnect } from 'react-redux-firebase';
import { connect } from 'react-redux';
import { compose } from 'redux';
import { PDFExport } from '#progress/kendo-react-pdf';
import { PdfButton } from 'components/StyledButtons';
import {
Container,
CircularProgress
} from '#material-ui/core';
function ViewProject({ _project, props }) {
const exportBriefing = id => e => {
this[id].save();
};
return (
<Fragment>
{_project && (
<Container maxWidth="md">
<PdfButton onClick={exportBriefing(_project.id)} />
<PDFExport
pageTemplate={DocumentTemplate}
paperSize={'A4'}
margin="1.5cm"
scale={0.7}
fileName={'Project_Brief'}
ref={r => (this[_project.id] = r)}
>
<---HTML content here--->
</PDFExport>
</Container>
)}
{!_project && (
<CircularProgress />
)}
</Fragment>
);
}
const mapStateToProps = ({ firestore: { ordered } }) => ({
_project: ordered.project && ordered.project[0],
});
export default compose(
connect(mapStateToProps),
firestoreConnect(props => [
{ collection: 'projects', storeAs: 'project', doc: props.match.params.id },
])
)(ViewProject);
This is my error (where hY2MWyk4piTBjhpJDFIc is the 'id' of the doc):
Thanks in advance for any suggestions.

You have to use the useRefhook, to declare your ref as null and then get the value :
const myRef = useRef(null);
Then attach to the Component that you want to ref:
<Fragment>
{_project && (
<Container maxWidth="md">
<PdfButton onClick={exportBriefing(_project.id)} />
<PDFExport
pageTemplate={DocumentTemplate}
paperSize={'A4'}
margin="1.5cm"
scale={0.7}
fileName={ 'Project_Brief'}
ref={ myRef}
>
Check this docs to get more info related to useRef:

The this context of a functional component does not persist across renders.
You'll want to use the useState hook instead of this.

Thanks to Ricardo Gonzalez for pointing me in the right direction. I was overthinking this and using useRef straight from the docs fixed the problem.
function ViewProject({ _project }) {
const inputEl = useRef(null);
const onButtonClick = () => {
inputEl.current.save();
};
return (
<Fragment>
{_project && (
<Container maxWidth="md">
<StyledPaper>
<PdfButton onClick={onButtonClick} />
<PDFExport
pageTemplate={DocumentTemplate}
paperSize={'A4'}
margin="1.5cm"
scale={0.7}
fileName={`Project_Brief: ${
_project.project_number
}`}
ref={inputEl}
>

Related

Component does not rerender when context changes

I've been playing around with the react context api and I'm just not getting why it's not working.
I have a component with a container that should show or hide depending on a valuer stored in context.
This is the component:
import React, { useContext } from 'react';
import ResultsContext from '../../context/results/resultsContext';
const ResultsPanelContainer = () => {
const resultsContext = useContext(ResultsContext);
const { showResults } = resultsContext;
console.log('showResults in ResultsPanelConatiner: ', showResults);
return (
<div
className='container-fluid panel'
style={{ display: showResults ? 'block' : 'none' }}
>
<div className='container'>
<div className='row'>
<div className='col'>
<h1 className='display-4'>Results.Panel.js</h1>
</div>
</div>
</div>
</div>
);
};
export default ResultsPanelContainer;
For completeness, the context is divided up into three sections, the call to the context itself, a 'state' file and a reducer. These are displayed below:
resultsContext.js
import { createContext } from 'react';
const resultsContext = createContext();
export default resultsContext;
ResultsState.js
import React, { useReducer } from 'react';
// import axios from 'axios';
import ResultsContext from './resultsContext';
import ResultsReducer from './resultsReducer';
import { UPDATE_SHOW_RESULTS } from '../types';
const ResultsState = (props) => {
const initialState = {
showResults: false,
};
const [state, dispatch] = useReducer(ResultsReducer, initialState);
const updateShowResults = (data) => {
console.log('updateShowResults - ', data);
dispatch({
type: UPDATE_SHOW_RESULTS,
payload: data,
});
};
return (
<ResultsContext.Provider
value={{
showResults: state.showResults,
updateShowResults,
}}
>
{props.children}
</ResultsContext.Provider>
);
};
export default ResultsState;
resultsReducer.js
import { UPDATE_SHOW_RESULTS } from '../types';
export default (state, action) => {
switch (action.type) {
case UPDATE_SHOW_RESULTS:
return {
...state,
showResults: action.payload,
};
default:
return state;
}
};
The change is triggered by a button click in a separate component and this does trigger an update in the context as shown when you log it to the console. However, the component is not rerendering.
I understand from reading various answers on here that changing context doesn't trigger a rerender of all child components in the same way that setState does. However, the component displaying this is calling the context directly so as far as I can see the rerender should take effect.
Am I missing something glaringly obvious?
Thanks in advance.
Stef
Forget the above... I'm an idiot - wrapped the two separate parts of the app in two separate instances of ResultsState which weren't communicating. Did this:
const App = () => {
return (
<Fragment>
<UsedDataState>
<Header />
</UsedDataState>
<main>
<ExportPanelContainer />
<ResultsState>
<SendQueryState>
<OrQueryState>
<AndQueryState>
<QueryPanelContainer />
</AndQueryState>
</OrQueryState>
</SendQueryState>
</ResultsState>
<ResultsState>
<ResultsPanelContainer />
</ResultsState>
</main>
</Fragment>
);
};
Instead of this:
const App = () => {
return (
<Fragment>
<UsedDataState>
<Header />
</UsedDataState>
<main>
<ExportPanelContainer />
<ResultsState>
<SendQueryState>
<OrQueryState>
<AndQueryState>
<QueryPanelContainer />
</AndQueryState>
</OrQueryState>
</SendQueryState>
<ResultsPanelContainer />
</ResultsState>
</main>
</Fragment>
);
};
Hope this is useful for someone else...

React useState() & useContext() - TypeError: setState is not a function - What am I doing wrong?

I've got a few React functional Components that I would like to share a state. In this example two toggle buttons that would conditionally show/hide a searchbar and a navbar.
--Solution, based on the accepted answer, on the bottom--
I'm completely new to useContext() and I keep running into the following error in the console:
Uncaught TypeError: setSearchbarToggle is not a function This goes for both buttons.
Bellow I have a filtered example code. It is just for the example I use the states in one file. In real life I would re-use the states in multiple functional components.
This is my header.js
import React, { useState, useContext } from "react"
import "./header.sass"
import { Context } from "./HeaderContext"
export const Header = () => {
const headerContext = useContext(Context)
const { navbarToggle, setNavbarToggle, searchbarToggle, setSearchbarToggle } = headerContext
return (
<React.Fragment>
<div className={"sticky-top"}>
<button onClick={ () => setNavbarToggle( !navbarToggle )}> Toggle Menu </button>
<button onClick={ () => setSearchbarToggle( !searchbarToggle )}> Toggle Search </button>
{navbarToggle && <h3>Menu is showing</h3>}
{searchbarToggle && <h3>Searchbar is showing</h3>}
</div>
</React.Fragment>
)
}
export default Header
And this is my HeaderContext.jsx
import React, { createContext, useState } from "react";
import PropTypes from "prop-types";
export const Context = createContext({});
export const Provider = props => {
const {
navbarToggle: initialNavBarToggle,
searchbarToggle: initialSarchbarToggle,
children
} = props;
const [navbarToggle, setNavbarToggle] = useState(initialNavBarToggle);
const [searchbarToggle, setSearchbarToggle] = useState(initialSarchbarToggle);
const headerContext = {
navbarToggle, setNavbarToggle,
searchbarToggle, setSearchbarToggle
};
return <Context.Provider value={headerContext}>{children}</Context.Provider>;
};
export const { Consumer } = Context;
Provider.propTypes = {
navbarToggle: PropTypes.bool,
searchbarToggle: PropTypes.bool
};
Provider.defaultProps = {
navbarToggle: false,
searchbarToggle: false
};
I hope you can shed some light on this for me
--edit--
This is my code based on the accepted answer.
import React, { useContext } from "react"
import { Provider,Context } from "./HeaderContext"
export const HeaderWithContext= () => {
const headerContext = useContext(Context)
const { navbarToggle, setNavbarToggle, searchbarToggle, setSearchbarToggle } = headerContext
return (
<React.Fragment>
<div className={"sticky-top"}>
<button onClick={ () => setNavbarToggle( !navbarToggle )}> Toggle Menu </button>
<button onClick={ () => setSearchbarToggle( !searchbarToggle )}> Toggle Search </button>
{navbarToggle && <h3>Menu is showing</h3>}
{searchbarToggle && <h3>Searchbar is showing</h3>}
</div>
</React.Fragment>
)
}
export const Header = () => {
return (
<Provider>
<HeaderWithContext/>
</Provider>
)
};
One of the parent components, e.g. App, must wrap the header (or one of its ancestor components) with Context.Provider:
import { Provider } from "./HeaderContext"
...
<Provider>
<Header />
</Provider>

React: Cannot update a component from inside the function body of a different component

i'm trying to only render the component <IntercomClient /> after a user clicks "Accept" on a cookie consent banner. Clicking accept changes the GlobalLayout's intercomIsActive state to true and thereby renders the IntercomClient. This is working but the warning concerns me.
How can I workaround the child/parent state change? I've been looking around but don't really understand.
import React, { useState } from 'react'
import { CookieBanner } from '#palmabit/react-cookie-law'
import IntercomClient from '../components/intercomClient'
const GlobalLayout = ({ location, children }) => {
const [intercomIsActive, setIntercomIsActive] = useState(false)
return (
...
<CookieBanner
onAccept={() => setIntercomIsActive(true)}
/>
<IntercomClient active={intercomIsActive}/>
...
)}
IntercomClient
import React from 'react';
import Intercom from 'react-intercom'
const IntercomClient = ({ active }) => {
return active ? <div><Intercom appID="XXXXXX" /></div> : null
}
export default IntercomClient;
import React, {useState} from 'react';
const Example = () => {
const [intercomIsActive, setIntercomIsActive] = useState(false)
return (
<Layout>
...
<CookieBanner
onAccept={() => setIntercomIsActive(true)}
/>
<IntercomClient active={intercomIsActive}/>
...
</Layout>
);
};
export default Example;
import React, {useState} from 'react';
const Example = () => {
const [intercomIsActive, setIntercomIsActive] = useState(false)
return (
<Layout>
...
<CookieBanner
onAccept={() => setIntercomIsActive(true)}
/>
{
intercomIsActive &&
<IntercomClient active={intercomIsActive}/>
}
...
</Layout>
);
};
export default Example;

React complains element type is invalid when trying to use context

I'm trying to use React Context to update navbar title dynamically from other child components. I created NavbarContext.js as follows. I have wrapped AdminLayout with NavContext.Provider and use useContext in Course.js to dynamically update navbar title inside useEffect. However, when I'm doing this, react throws the following error on the screen.
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.
How can I use context properly so that I can update Header title from Course.js inside its useEffect?
NavbarContext.js
import React, {useState} from 'react'
export default () => {
const [name,setName] = useState("")
const NavContext = React.createContext({
name: "",
changeName: name => setName(name)
})
const NavProvider = NavContext.Provider
const NavConsumer = NavContext.Consumer
return NavContext
}
AdminLayout.js
<NavContext.Provider>
<div className={classes.wrapper}>
<Sidebar
routes={routes}
logoText={"Widubima"}
logo={logo}
image={image}
handleDrawerToggle={handleDrawerToggle}
open={mobileOpen}
color={color}
{...rest}
/>
<div className={classes.mainPanel} ref={mainPanel}>
<Navbar
routes={routes}
handleDrawerToggle={handleDrawerToggle}
{...rest}
/>
{/* On the /maps route we want the map to be on full screen - this is not possible if the content and conatiner classes are present because they have some paddings which would make the map smaller */}
{getRoute() ? (
<div className={classes.content}>
<div className={classes.container}>{switchRoutes}</div>
</div>
) : (
<div className={classes.map}>{switchRoutes}</div>
)}
</div>
</div>
</NavContext.Provider>
Navbar.js
import NavContext from "context/NavbarContext"
export default function Header(props) {
function makeBrand() {
var name;
props.routes.map(prop => {
if (window.location.href.indexOf(prop.layout + prop.path) !== -1) {
name = prop.name;
document.title = name;
}
return null;
});
return name;
}
return (
<AppBar className={classes.appBar + appBarClasses}>
<Toolbar className={classes.container}>
<div className={classes.flex}>
{/* Here we create navbar brand, based on route name */}
<NavContext.Consumer>
{({ name, setName }) => (
<Button
color="transparent"
href="#"
className={classes.title}
style={{ fontSize: "1.5em", marginLeft: "-2%" }}
>
{makeBrand() || name}
</Button>
)}
</NavContext.Consumer>
</Toolbar>
</AppBar>
);
}
Course.js
import React, { useState, useEffect, useContext } from "react";
import NavContext from "context/NavbarContext"
const AdminCourse = props => {
const context = useContext(NavContext);
useEffect(() => {
Axios.get('/courses/'+props.match.params.courseId).then(
res => {
context.changeName("hello")
}
).catch(err => {
console.log(err)
})
return () => {
setCourseId("");
};
});
return (
<GridContainer>
</GridContainer>
);
};
export default AdminCourse;
i think problem is there with your NavbarContext.js.
you are not exporting NavContext also.
you are defining provider, consumer but you are not using them either.
here's how you can solve your problem.
first create context and it's provider in a file as following.
NavContext.js
import React, { useState } from "react";
const NavContext = React.createContext();
const NavProvider = props => {
const [name, setName] = useState("");
let hookObject = {
name: name,
changeName: setName
};
return (
<NavContext.Provider value={hookObject}>
{props.children}
</NavContext.Provider>
);
};
export { NavProvider, NavContext };
in above code first i am creating context with empty value.
the i am creating NavProvider which actually contains value name as a state hook inside it.hookObject exposes state as per your naming conventions in code.
now i for testing purpose i defined two consumers.
one is where we update name in useEffect, that is ,
ConsumerThatUpdates.js
import React, { useContext, useEffect } from "react";
import { NavContext } from "./NavContext";
const ConsumerThatUpdates = () => {
const { changeName } = useContext(NavContext);
useEffect(() => {
changeName("NEW NAME");
}, [changeName]);
return <div>i update on my useeffect</div>;
};
export default ConsumerThatUpdates;
you can update useEffect as per your needs.
another is where we use the name,
ConsumerThatDisplays.js
import React, { useContext } from "react";
import { NavContext } from "./NavContext";
const ConsumerThatDisplays = () => {
const { name } = useContext(NavContext);
return <div>{name}</div>;
};
export default ConsumerThatDisplays;
and finally my App.js looks like this,
App.js
import React from "react";
import "./styles.css";
import { NavProvider } from "./NavContext";
import ConsumerThatDisplays from "./ConsumerThatDisplays";
import ConsumerThatUpdates from "./ConsumerThatUpdates";
export default function App() {
return (
<div className="App">
<NavProvider>
<ConsumerThatDisplays />
<ConsumerThatUpdates />
</NavProvider>
</div>
);
}
hope this helps!!
if you want to know more about how to use context effectively, i recooHow to use React Context effectively

Call method from child (class based component) in parent (functional component)

I have been developing a project for three months. I need to call a method from the child component (class-based component) in the parent component (functional component). I used ref for this but it didn't work.
Here is the parent component:
import React, { useState, useEffect, useRef, createRef } from "react";
import CityPicker from "../../components/public/cityPicker";
import Chip from "../../components/forms/chip";
import Sidebar from "./sidebar";
import { Router } from "../../routes";
import NextRouter, { withRouter } from "next/router";
const Search = props => {
const [filterItem, setFilterItem] = useState();
const deleteFilterItem = createRef();
const deleteChipHandler = event => {
filterItem
? deleteFilterItem.current.onDeleteSearchableFilterItem(
event,
"stateOrProvince",
"selectedStateOrProvince"
)
: "";
};
return (
<>
<div className={filterItem ? "filters-display" : ""}>
{filterItem
? filterItem.map((item, index) => {
return (
<Chip
id={item.id}
key={index + "selected city"}
label={item.value}
onDelete={e => deleteChipHandler(e)}
/>
);
})
: ""}
</div>
<Sidebar ref={deleteFilterItem} />
</>
);
};
export default withRouter(Search);
The onDeleteSearchableFilterItem method belong to the child component.
Write the function like this in your child component. data is the data you get from your state of that component or a state from reducer if you are using redux(like this.props.data):
const onDelete = () => {
this.props.onDelete(data);
}
then call it in the parent:
import { Router } from "../../routes";
import NextRouter, { withRouter } from "next/router";
const Search = props => {
const [filterItem, setFilterItem] = useState();
const deleteFilterItem = createRef();
const deleteChipHandler = (event, data) => {
filterItem
? deleteFilterItem.current.onDeleteSearchableFilterItem(
event,
data
)
: null;
};
return (
<>
<div className={filterItem ? "filters-display" : ""}>
{filterItem
? filterItem.map((item, index) => {
return (
<Chip
id={item.id}
key={index + "selected city"}
label={item.value}
onDelete={deleteChipHandler}
/>
);
})
: ""}
</div>
<Sidebar ref={deleteFilterItem} />
</>
);
};
export default withRouter(Search);

Resources