Unexpected token '<' in React Context Provider - reactjs

I'm trying to implement a language context in a React App, however when I try to pass some elements into the value prop, it won't work.
The error is: Unexepect token in line 2:2 (i.e.). But the tags are well closed. Am I missing something in value which is absolutely required?
const LanguageProvider = ({ children }) => (
<LanguageContext.Provider value={{
setLanguage,
translations
}}
>
{children}
</LanguageContext.Provider>
);
setLanguage comes from:
const [language, setLanguage] = useState('en_US');
and translations equals to an object of strings:
const translations = {
en_US: {
settings: {
menu: 'Main Menu',
screen: 'Screen'
}
},
es_MX: {
settings: {
menu: 'Menú Principal',
screen: 'Pantalla'
}
}
};

The tags are okay, you just need to move setLanguage into LanguageProvider.
const LanguageProvider = ({ children }) => {
const [language, setLanguage] = useState("en_US");
return (
<LanguageContext.Provider
value={{
setLanguage,
translations
}}
>
{children}
</LanguageContext.Provider>
);
};
I have a sandbox you can try.

Related

React Native - Getting values from useContext - issue with objects/functions

I've issues with setting and getting values from my Context provider. Code below works, I am getting the value "2" in my other screen through useContext(). But it only works when it's hardcoded as below.
export const PinfoContext = createContext({});
export const PinfoProvider = ({ children }) => {
const [px, setPx] = useState(null);
return (
<PinfoContext.Provider
value={{
px: 2,
setPx,
}}
>
{children}
</PinfoContext.Provider>
);
};
export default function Screen2({ route, navigation }) {
const { px } = useContext(PinfoContext);
return (
<View>
<Text>Test-- {px}</Text>
</View>
);
}
If I was to change my provider something like below; I cant seem to set/get "px" values to "aaa" from my provider. What would be the correct way to do this? Logic here is that I am going to get objects from my db and use it in my other screen.
export const PinfoContext = createContext({});
export const PinfoProvider = ({ children }) => {
const [px, setPx] = useState(null);
return (
<PinfoContext.Provider
value={{
px,
setPx,
function: () => {
setPx("aaa");
},
}}
>
{children}
</PinfoContext.Provider>
);
};

Setting value for React context values with React testing library

I am working on testing a component using react-testing-library. The component has an alert which accepts a prop that comes from context to determine whether the alert is open or not.
const PersonRecord = () => {
const {
personSuccessAlert,
setPersonSuccessAlert,
updatePersonSuccessAlert,
setUpdatePersonSuccessAlert,
} = useContext(PeopleContext);
return (
{personSuccessAlert && (
<div className="person-alert-container">
<Alert
ariaLabel="person-create-success-alert"
icon="success"
open={personSuccessAlert}
/>
</div>
)}
)
}
So the above code uses context to pull the value of personSuccessAlert from PeopleContext. If personSuccessAlert is true the alert will display. My context file is set up as follows:
import React, { createContext, useState } from 'react';
export const PeopleContext = createContext();
const PeopleContextProvider = ({ children }) => {
const [personSuccessAlert, setPersonSuccessAlert] = useState(false);
const [updatePersonSuccessAlert, setUpdatePersonSuccessAlert] = useState(
false,
);
return (
<PeopleContext.Provider
value={{
personSuccessAlert,
updatePersonSuccessAlert,
setPersonSuccessAlert,
setUpdatePersonSuccessAlert,
}}>
{children}
</PeopleContext.Provider>
);
};
export default PeopleContextProvider;
Now I am trying to develop a test which passes personSuccessAlert = true to the PersonRecord component.
Here is what I have been trying:
export function renderWithEmptyPerson(
ui,
{
providerProps,
path = '/',
route = '/',
history = createMemoryHistory({ initialEntries: [route] }),
},
) {
return {
...render(
<MockedProvider mocks={getEmptyPerson} addTypename={false}>
<PeopleContextProvider {...providerProps}>
<Router history={history}>
<Route path={path} component={ui} />
</Router>
</PeopleContextProvider>
</MockedProvider>,
),
};
}
describe('empty person record rendering', () => {
afterEach(cleanup);
test('empty person', async () => {
const providerProps = { value: true };
const { getByText, queryByText, queryByLabelText } = renderWithEmptyPerson(
PersonRecord,
{
providerProps,
route: 'people/6d6ed1f4-8294-44de-9855-2999bdf9e3a7',
path: 'people/:slug',
},
);
expect(getByText('Loading...')).toBeInTheDocument();
});
});
I have tried different variations of const providerProps = { value: true };. Replacing value with personSuccessAlert did not work.
Any advice or help is appreciated.
You are passing providerProps to the PeopleContextProvider, but the PeopleContextProvider is not doing anything with the props. You'll need to actually use those props, for example to set the initial state. You could try something like:
const PeopleContextProvider = ({ children, initialPersonSuccessAlert = false }) => {
const [personSuccessAlert, setPersonSuccessAlert] = useState(initialPersonSuccessAlert);
const [updatePersonSuccessAlert, setUpdatePersonSuccessAlert] = useState(
false,
);
return (
<PeopleContext.Provider
value={{
personSuccessAlert,
updatePersonSuccessAlert,
setPersonSuccessAlert,
setUpdatePersonSuccessAlert,
}}>
{children}
</PeopleContext.Provider>
);
};
This would allow you to set the initial state of personSuccessAlert by passing in a initialPersonSuccessAlert prop. You could update your test like so:
const providerProps = { initialPersonSuccessAlert: true };
Alternatively, if you only wanted to make changes in your test file, you could consider updating the renderWithEmptyPerson function to use PeopleContext.Provider directly instead of the PeopleContextProvider component. That will allow you to set the context value however you like.

I dont know how to hunt down my problem - React

Im very new to React and im having an issue Im not sure how to troubleshoot. So im setting an array on the context when a http request fails in a custom hook
Here is my hook:
const useHttp = (requestObj: any, setData: Function) =>
{
const [isLoading, setIsLoading] = useState(false);
const ctx = useContext(GlobalContext);
const sendRequest = useCallback(() =>
{
setIsLoading(true);
fetch(requestObj.url, {
method: requestObj.method ? requestObj.method: 'GET',
headers: requestObj.headers ? requestObj.headers : {},
body: requestObj.body ? JSON.stringify(requestObj.body) : null
})
.then(res => res.json())
.then(data => {
setIsLoading(false);
setData(data);
})
.catch(err =>
{
setIsLoading(false);
ctx.setErrors([
(prevErrors: string[]) =>
{
//prevErrors.push(err.message)
let newArray = prevErrors.map((error) => {return error});
newArray.push(err.message);
return newArray;
}]
);
console.log('There was an error');
});
}, []);
return {
isLoading: isLoading,
sendRequest: sendRequest
}
}
Im using .map cos the spread operator for arrays isnt working. Im looking into it but its not important for this.
When there are errors I create a modal and then render it in my jsx. My problem is that for some reason my Modal is rendering twice. The second time it has no props and that blows up my program. I dont know why its rendering again and I dont know how to attack the problem. The stack has nothing with regards to what is causing it (that I can see). If a component is rendering again would the props not be the same as originally used? I have breakpoints in the spot where the modal is called and they arent getting hit again. So can anyone offer some advice for how I go about debugging this?
const App: FC = () => {
const [errors, setErrors] = useState([]);
let modal = null
if(errors.length > 0)
{
modal = (
<Modal
heading="Warning"
content={<div>{errors}</div>}
buttonList={
[
{label: "OK", clickHandler: ()=> {}, closesModal: true},
{label: "Cancel", clickHandler: ()=> {alert("cancelled")}, closesModal: false}
]
}
isOpen={true}/>
)
}
return (
<GlobalContext.Provider value={{errors: errors, setErrors: setErrors}}>
<ProviderV3 theme={defaultTheme}>
<Toolbar></Toolbar>
<Grid
margin='25px'
columns='50% 50%'
gap='10px'
maxWidth='100vw'>
<OwnerSearch />
<NewOwnerSearch />
</Grid>
</ProviderV3>
{modal}
</GlobalContext.Provider>
);
};
import { FC, useState } from 'react';
import {
ButtonGroup, Button, DialogContainer,
Dialog, Content, Heading, Divider
} from '#adobe/react-spectrum';
type Props = {
heading: string,
content : any,
buttonList: {label: string, clickHandler: Function, closesModal: boolean}[],
isOpen: boolean
}
const Modal: FC<Props> = ( props ) =>
{
const [open, setOpen] = useState(props.isOpen);
let buttons = props.buttonList.map((button, index) =>
{
return <Button key={index} variant="cta" onPress={() => close(button.clickHandler, button.closesModal)} autoFocus>
{button.label}
</Button>
});
const close = (clickHandler: Function | null, closesModal: boolean) =>
{
if(clickHandler != null)
{
clickHandler()
}
if(closesModal)
{
setOpen(false)
}
}
return (
<DialogContainer onDismiss={() => close(null, true)} >
{open &&
<Dialog>
<Heading>{props.heading}</Heading>
<Divider />
<Content>{props.content}</Content>
<ButtonGroup>
{buttons}
</ButtonGroup>
</Dialog>
}
</DialogContainer>
);
}
export default Modal;
Following Firefighters suggestion I get an error now:
Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.
at resolveDispatcher (react.development.js:1476)
at useContext (react.development.js:1484)
at useProvider (module.js:239)
at $bc3300334f45fd1ec62a173e70ad86$var$Provider (module.js:95)
at describeNativeComponentFrame (react-dom.development.js:946)
at describeFunctionComponentFrame (react-dom.development.js:1034)
at describeFiber (react-dom.development.js:1119)
at getStackByFiberInDevAndProd (react-dom.development.js:1138)
at createCapturedValue (react-dom.development.js:20023)
at throwException (react-dom.development.js:20351)
Try putting the open state inside the App component and remove it from the Modal component:
const [errors, setErrors] = useState([]);
const [isModalOpen, setIsModalOpen] = useState(false);
useEffect(() => {
if(errors.length > 0) setIsModalOpen(true);
}, [errors])
<Modal
...
isOpen={isModalOpen}
setIsOpen={setIsModalOpen}
/>

How to render a react template?

I'm creating a web app that serves the user unique pages with a substantial amount of JSX e.g.
<Explanation>
<p>Here will be a lengthy section explaining how to solve a problem that I want rendered using the explanation component</p>
<Quote>It might also have child components in it</Quote>
</Explanation>
<Questions>Here will be some questions I've written</Questions>
<Image> Here will be an image that I want to render using my image component</Image>
Ideally I would like to store the JSX in a mongo database as an object like this:
post = {
_id: 1,
content: `<Explanation>
<p>Here will be a lengthy section explaining how to solve a problem that I want rendered using the explanation component</p>
<Quote>It might also have child components in it</Quote>
</Explanation>
<Questions>Here will be some questions I've written</Questions>
<Image> Here will be an image that I want to render using my image component</Image>`
because I will have many hundreds of such pages and the user should receive only a particular page.
Is there a way I can achieve this?
At the moment I have resigned to writing an object like this:
post = {
_id,
content: [
{component: Explanation,
props: {key: 'bla'},
content: [
{component: p,
props: null,
content: 'Here will be a lengthy section explaining how to solve a problem that I want rendered using the explanation component'
}
]
}
]
and then writing a function that turns them into React elements like so:
const renderPost = (post) => {
const JSX = post.content.map(c =>
React.createElement(c.component, c.props, c.content)
)
return JSX
}
but this whole process feels cumbersome and inefficient.
Is there a better way of trying to achieve my goal?
This is a quick work around.
import React from "react";
import "./styles.css";
var str = `
<Explanation>
<p>this is ex1 p tag</p>
<Quote>this is quote</Quote>
</Explanation>
<Questions>This is q</Questions>
<Image>this is img</Image>
`;
const createMarkup = (props) => ({ __html: props });
const Custom = ({ children }) => {
return <div dangerouslySetInnerHTML={createMarkup(children)} />;
};
const Quote = ({ children }) => {
return <blockquote dangerouslySetInnerHTML={createMarkup(children)} />;
};
const Questions = ({ children }) => {
return <q dangerouslySetInnerHTML={createMarkup(children)} />;
};
const Image = ({ children }) => {
return <h1 dangerouslySetInnerHTML={createMarkup(children)} />;
};
const Explanation = ({ children }) => {
return (
<div className="explain" dangerouslySetInnerHTML={createMarkup(children)} />
);
};
const cpmponentToShow = [];
const components = {
Custom,
Explanation,
Image,
Questions,
Quote
};
const componetCreate = (string) => {
const tags = string.match(/(?<=<)((\w|\b)+)[^>]/g);
if (tags.length) {
tags.map((tag) => {
var regexNew = `<${tag}>([\\s\\S]*?)</${tag}>`;
var matched = string.match(regexNew);
const leftOver = matched[1].match(/(?<=<)(\w+)[^>]/g);
if (!leftOver) {
cpmponentToShow.push(
React.createElement(
components[tag] ? components[tag] : tag,
null,
matched[1]
)
);
}
return null;
});
}
};
componetCreate(str);
export default function App() {
return (
<div className="App">
{cpmponentToShow.map((item, index) => (
<React.Fragment key={index}>{item}</React.Fragment>
))}
</div>
);
}
p {
color: red;
}
blockquote {
color: black;
}
q {
color: purple;
}
h1 {
color: royalblue;
}
.explain {
color: salmon;
}
sandbox link

React test not working with current Jest config, Gatsby and gatsby-theme-i18n

I am fairly new to testing with Gatsby and Jest and I am encountering issues with my current setup.
I am trying to build a site with Gatsby and I would like for CI purposes to run unit tests with Jest and react-testing-library.
I initially was able to run a simple test:
describe('<Header>', () => {
describe('mounts', () => {
test('component mounts', () => {
const { container } = render(
<Header />
);
expect(container).toBeInTheDocument();
});
});
describe('click', () => {
const { container } = render(<Header />);
expect(screen.getByText('Light mode ☀')).toBeInTheDocument();
fireEvent(
getByText(container, 'Light mode ☀'),
new MouseEvent('click', {
bubbles: true,
cancelable: true,
}),
);
expect(screen.getByText('Dark mode ☾')).toBeInTheDocument();
});
where my render is a customized one:
const AllTheProviders = ({ children }) => {
const [dark, setDark] = useState(true);
return (
<ThemeContext.Provider
value={{
dark,
toggleDark: () => setDark(!dark),
}}
>
{children}
</ThemeContext.Provider>
);
};
const customRender = (ui, options) =>
render(ui, { wrapper: AllTheProviders, ...options });
// re-export everything
export * from '#testing-library/react';
// override render method
export { customRender as render };
and the Header a simple component that consumes the context and toggles the theme from dark to light.
I since then added gatsby-theme-i18n to handle i18n and tweaked my jest config to add:
transformIgnorePatterns: ['node_modules/(?!(gatsby-theme-i18n)/)']
The Header is now wrapped in a Layout component that provides the locale and location (with the Help for Location and #reach/router that comes with Gatsby) so I can have a language toggle in the header
In the Layout:
import { Location } from '#reach/router';
[...]
<Location>
{(location) => (
<Header
siteTitle={data.site.siteMetadata.title}
location={location}
locale={locale}
/>
)}
</Location>
The Header makes use of LocalizedLink from gatsby-theme-i18n` to prefix the right locale
<li key={langKey}>
<LocalizedLink key={langKey} to={to} language={langKey}>
{langKey}
</LocalizedLink>
</li>
I also changed the test to:
describe('mounts', () => {
test('component mounts', () => {
const { container } = render(
<Location>
{(location) => (
<Header siteTitle="site title" location={location} locale="en" />
)}
</Location>,
);
expect(container).toBeInTheDocument();
});
});
However, I get an error when trying to run the test:
TypeError: Cannot read property 'themeI18N' of undefined
21 |
22 | const customRender = (ui, options) =>
> 23 | render(ui, { wrapper: AllTheProviders, ...options });
| ^
specifically coming from at useLocalization (node_modules/gatsby-theme-i18n/src/hooks/use-localization.js:9:7)
where the Gatsby theme provides the i18n info through a graphQL query:
const {
themeI18N: { defaultLang, config },
} = useStaticQuery(graphql`
{
themeI18N {
defaultLang
config {
code
hrefLang
dateFormat
langDir
localName
name
}
}
}
`)
I understand that Jest does not 'understand' what to do with the i18n passed through the gatsby-theme-i18n via the Layout and if I switch my LocalizedLink to a normal Gatsby Link, the test passes (and I can console.log the container to see it's indeed a React element with the right info).
I tried a few things I saw online like using const { useLocalization } = require('gatsby-theme-i18n'); but to no avail.
Any idea how to handle this?
Should I mock the utils from gatsby-theme-i18n like other elements from Gatsby are mocked (Link etc.) following the docs?
EDIT
Thanks to the author of the library, LekoArts, and a friend a mine, I managed to make it work :)
If somebody has the same issue, you need to mock the response from the useStaticQuery of the useLocalization hook.
I have a mock file that does it:
// Libs
const React = require('react');
const gatsby = jest.requireActual('gatsby');
// Utils
const siteMetaData = require('../src/utils/siteMetaData');
module.exports = {
...gatsby,
graphql: jest.fn(),
Link: jest.fn().mockImplementation(
// these props are invalid for an `a` tag
({
activeClassName,
activeStyle,
getProps,
innerRef,
partiallyActive,
ref,
replace,
to,
...rest
}) =>
React.createElement('a', {
...rest,
href: to,
}),
),
StaticQuery: jest.fn(),
useStaticQuery: jest.fn().mockImplementation(() => ({
site: {
siteMetadata: {
...siteMetaData,
},
},
themeI18N: {
defaultLang: 'en',
config: {
code: 'en',
hrefLang: 'en-CA',
name: 'English',
localName: 'English',
langDir: 'ltr',
dateFormat: 'MM/DD/YYYY',
},
},
})),
};
and my test:
// Libs
import React from 'react';
// Utils
import { Location } from '#reach/router';
import { render, fireEvent, getByText, screen } from './utils/test-utils';
// Component
import Header from '../header';
describe('<Header>', () => {
describe('mounts', () => {
test('component mounts', () => {
const { container } = render(
<Location>
{(location) => (
<Header siteTitle="site title" location={location} locale="en" />
)}
</Location>,
);
expect(container).toBeInTheDocument();
});
});
describe('click', () => {
test('toggle language', () => {
const { container } = render(
<Location>
{(location) => (
<Header siteTitle="site title" location={location} locale="en" />
)}
</Location>,
);
expect(screen.getByText('Light mode ☀')).toBeInTheDocument();
fireEvent(
getByText(container, 'Light mode ☀'),
new MouseEvent('click', {
bubbles: true,
cancelable: true,
}),
);
expect(screen.getByText('Dark mode ☾')).toBeInTheDocument();
});
});
});
You can find more information here

Resources