How to use react native navigation wix with context to share data from provider to all components? - hook

I want to use react-native-navigation with context to send data from provider to all my components. I have done it as follow:
index.js
import {Navigation} from 'react-native-navigation';
import {App} from './App'
App();
Navigation.events().registerAppLaunchedListener(() => {
Navigation.setRoot({
root: {
stack:
{
id: 'FoodApp',
children: [
{
component: {
id: 'myLoginId',
name: 'myLogin',
options: {
topBar: {
visible: false,
height: 0,
},
}
}
},
],
},
},
});
});
App.js
export const App = () => {
Navigation.registerComponent(`myLogin`, () => (props) => (
<GlobalProvider>
<Login {...props} />
</GlobalProvider>
), () => Login);
Navigation.registerComponent(`myMain`, () => (props) => (
<GlobalProvider>
<Main {...props} />
</GlobalProvider>
), () => Main);
};
GlobalProvider and GlobalContext
const GlobalContext = React.createContext();
export const GlobalProvider = ({children}) => {
return <GlobalContext.Provider value={20}>
{children}
</GlobalContext.Provider>;
};
export default GlobalContext;
Main.js
const Main = () => {
const value= useContext(GlobalContext);
const options = (passProps) => {
return {
topBar: {
height: 0,
},
};
};
return (
<View style={styles.mainContainer}>
<Text>Main {value}</Text>
</View>
);
};
It does not give any errors but it does not show value. Please help me how to fix it. I have searched a lot but I cannot find any useful thing.
I have used:
"react": "16.11.0",
"react-native": "0.62.0",
"react-native-navigation": "^6.3.3",

it's weird that the value is not showing at all as your code snippet should display value. I've created an example below to demonstrate integrating React Context with react-native-navigation but unfortunately as react-native-navigation is not a single root application (each registered screen is a "root") the regular Context pattern would not work as expected.
// CounterContext.js
import React from 'react
const initialCounterState = {
count: 0
}
const counterContextWrapper = (component) => ({
...initialCounterState,
increment: () => {
initialCounterState.count += 1
component.setState({ context: contextWrapper(component) })
},
decrement: () => {
initialCounterState.count -= 1
component.setState({ context: contextWrapper(component) })
},
})
export const CounterContext = React.createContext({})
export class CounterContextProvider extends React.Component {
state = {
context: counterContextWrapper(this)
}
render() {
return (
<CounterContext.Provider value={this.state.context}>
{this.props.children}
</CounterContext.Provider>
)
}
}
// index.js
import { Navigation } from 'react-native-navigation
import { CounterContextProvider } from './CounterContext
import { Main } from './Main
Navigation.registerComponent(
'Main',
() => props => (
<CounterContextProvider>
<Main {...props} />
</CounterContextProvider>
),
() => CounterReactContextScreen
)
// Main.js
import React from 'react'
import { Button, Text, View } from 'react-native'
import { CounterContext } from './CounterContext'
export const Main = () => {
const { count, increment, decrement } = React.useContext(CounterContext)
return (
<View>
<Text>{`Clicked ${count} times!`}</Text>
<Button title="Increment" onPress={increment} />
<Button title="Decrement" onPress={decrement} />
</View>
)
}
This should all work, however the caveat is if you register 2 screens with the same Context Provider (for example, Main as your root and Pushed as a screen that gets pushed from Main) if you update the value on Pushed screen, it would not re-render Main screen to show the updated value.
I'd recommend to use MobX if you want Context like API. You could checkout my boilerplate project https://github.com/jinshin1013/rnn-boilerplate.

Related

Adminjs adding custom reactjs components to my resource on adminjs

I am using adminjs for the first time and I have written my own custom component, but adding it to the resource keeps producing an error that says "You have to implement action component for your Action" Here is my resource:
const {Patient} = require('./../models/Patient')
const Components = require('./../components/components')
const { Patient } = require('./../models/Patient')
const Components = require('./../components/components')
const PatientResource = {
resource: Patient,
options: {
actions: {
consultation: {
actionType: 'record',
component: Components.MyCustomAction,
handler: (request, response, context) => {
const { record, currentAdmin } = context
return {
record: record.toJSON(currentAdmin),
msg: 'Hello world',
}
},
},
isAccessible: ({ currentAdmin }) => {
action: if (currentAdmin.role_id === 5) {
return false
} else {
return true
}
},
},
navigation: 'Manage Patients',
listProperties: [
'id',
'last_name',
'first_name',
'sex',
'marital_status',
'blood_group',
'genotype',
'existing_health_condition',
],
},
}
module.exports = { PatientResource }
And this is my custom component:
import React from 'react'
import { Box, H3 } from '#adminjs/design-system'
import { ActionProps } from 'adminjs'
const MyCustomActionComponent = (props: ActionProps) => {
const { record } = props
return (
<Box flex>
<Box
variant='white'
width={1 / 2}
boxShadow='card'
mr='xxl'
flexShrink={0}
>
<H3>Example of a simple page</H3>
<p>Where you can put almost everything</p>
<p>like this: </p>
<p>
<img
src='https://i.redd.it/rd39yuiy9ns21.jpg'
alt='stupid cat'
width={300}
/>
</p>
</Box>
<p> Or (more likely), operate on a returned record:</p>
<Box overflowX='auto'> {JSON.stringify(record)}</Box>
</Box>
)
}
module.exports = { MyCustomActionComponent }
I tried to add the component to my custom defined action "Consultation", Hence I expected to see a custom page which i have designed using react as in "mycustomAction" above

Jest testing error: Cannot read property 'off' of null. With react-signature-canvas

I have this react component which uses react-signature-canvas.
I want to add some tests but I get this error and Im ot sure how to fix it.
TypeError: Cannot read property 'off' of null
29 | describe("HandSignature", () => {
30 | it("checks for classname", () => {
> 31 | render(<Wrapper />);
| ^
32 | const btn = screen.getByTestId("save");
33 | expect(btn).toHaveClass("saveBtn");
34 | });
at t.r.off (node_modules/react-signature-canvas/build/index.js:1:3235)
The component I want to test is this:
import React, { useRef } from "react";
import SignatureCanvas from "react-signature-canvas";
...
const canvasProps = {
role: "img",
"aria-label": i18n.t("changeService:signature.signature"),
className: Styles.signature,
};
type Props = {
submit: Function,
refDom: Object,
signature: Object,
setSignature: Function,
};
const HandSignature = ({ submit, refDom, signature, setSignature }: Props) => {
const { t } = useTranslation("common");
const _resizedSigCanvas: Object = useRef({});
return (
<>
<div className={Styles.signatureCanvasWrapper}>
<div className={Styles.aspectRatio8x2}>
<div className={Styles.aspectRatioInnerWrapper}>
<div id="resizedCanvas" ref={_resizedSigCanvas}></div>
<SignatureCanvas ref={refDom} canvasProps={canvasProps} />
</div>
</div>
</div>
</>
);
};
export default HandSignature;
I read in the internet that I need to add the package jest-canvas-mock in order to test the canvas, So i added it to my package.json, I also added this to my package.json:
"jest": {
"setupFiles": ["jest-canvas-mock"]
}
But when I run it I get this error: Cannot read property 'off' of null
This is my test:
import "#testing-library/jest-dom/extend-expect";
import "jest-canvas-mock";
import { Form } from "react-final-form";
import React from "react";
import { screen, render } from "#testing-library/react";
import HandSignature from "./HandSignature";
// const state = {};
beforeEach(jest.resetAllMocks);
const Wrapper = () => {
const refDom = React.useRef({});
const props = {
submit: jest.fn(),
refDom,
signature: null,
setSignature: jest.fn(),
};
return (
<Form onSubmit={() => {}}>
{() => {
return <HandSignature {...props} />;
}}
</Form>
);
};
describe("HandSignature", () => {
it("test classname", () => {
render(<Wrapper />);
const btn = screen.getByTestId("save");
expect(btn).toHaveClass("saveBtn");
});
});
Any ideas on how to fix this?

next-i18next Jest Testing with useTranslation

Testing libs...always fun. I am using next-i18next within my NextJS project. We are using the useTranslation hook with namespaces.
When I run my test there is a warning:
console.warn
react-i18next:: You will need to pass in an i18next instance by using initReactI18next
> 33 | const { t } = useTranslation(['common', 'account']);
| ^
I have tried the setup from the react-i18next test examples without success. I have tried this suggestion too.
as well as just trying to mock useTranslation without success.
Is there a more straightforward solution to avoid this warning? The test passes FWIW...
test('feature displays error', async () => {
const { findByTestId, findByRole } = render(
<I18nextProvider i18n={i18n}>
<InviteCollectEmails onSubmit={jest.fn()} />
</I18nextProvider>,
{
query: {
orgId: 666,
},
}
);
const submitBtn = await findByRole('button', {
name: 'account:organization.invite.copyLink',
});
fireEvent.click(submitBtn);
await findByTestId('loader');
const alert = await findByRole('alert');
within(alert).getByText('failed attempt');
});
Last, is there a way to have the translated plain text be the outcome, instead of the namespaced: account:account:organization.invite.copyLink?
Use the following snippet before the describe block OR in beforeEach() to mock the needful.
jest.mock("react-i18next", () => ({
useTranslation: () => ({ t: key => key }),
}));
Hope this helps. Peace.
use this for replace render function.
import { render, screen } from '#testing-library/react'
import DarkModeToggleBtn from '../../components/layout/DarkModeToggleBtn'
import { appWithTranslation } from 'next-i18next'
import { NextRouter } from 'next/router'
jest.mock('react-i18next', () => ({
I18nextProvider: jest.fn(),
__esmodule: true,
}))
const createProps = (locale = 'en', router: Partial<NextRouter> = {}) => ({
pageProps: {
_nextI18Next: {
initialLocale: locale,
userConfig: {
i18n: {
defaultLocale: 'en',
locales: ['en', 'fr'],
},
},
},
} as any,
router: {
locale: locale,
route: '/',
...router,
},
} as any)
const Component = appWithTranslation(() => <DarkModeToggleBtn />)
const defaultRenderProps = createProps()
const renderComponent = (props = defaultRenderProps) => render(
<Component {...props} />
)
describe('', () => {
it('', () => {
renderComponent()
expect(screen.getByRole("button")).toHaveTextContent("")
})
})
I used a little bit more sophisticated approach than mocking to ensure all the functions work the same both in testing and production environment.
First, I create a testing environment:
// testing/env.ts
import i18next, { i18n } from "i18next";
import JSDomEnvironment from "jest-environment-jsdom";
import { initReactI18next } from "react-i18next";
declare global {
var i18nInstance: i18n;
}
export default class extends JSDomEnvironment {
async setup() {
await super.setup();
/* The important part start */
const i18nInstance = i18next.createInstance();
await i18nInstance.use(initReactI18next).init({
lng: "cimode",
resources: {},
});
this.global.i18nInstance = i18nInstance;
/* The important part end */
}
}
I add this environment in jest.config.ts:
// jest.config.ts
export default {
// ...
testEnvironment: "testing/env.ts",
};
Sample component:
// component.tsx
import { useTranslation } from "next-i18next";
export const Component = () => {
const { t } = useTranslation();
return <div>{t('foo')}</div>
}
And later on I use it in tests:
// component.test.tsx
import { setI18n } from "react-i18next";
import { create, act, ReactTestRenderer } from "react-test-renderer";
import { Component } from "./component";
it("renders Component", () => {
/* The important part start */
setI18n(global.i18nInstance);
/* The important part end */
let root: ReactTestRenderer;
act(() => {
root = create(<Component />);
});
expect(root.toJSON()).toMatchSnapshot();
});
I figured out how to make the tests work with an instance of i18next using the renderHook function and the useTranslation hook from react-i18next based on the previous answers and some research.
This is the Home component I wanted to test:
import { useTranslation } from 'next-i18next';
const Home = () => {
const { t } = useTranslation("");
return (
<main>
<div>
<h1> {t("welcome", {ns: 'home'})}</h1>
</div>
</main>
)
};
export default Home;
First, we need to create a setup file for jest so we can start an i18n instance and import the translations to the configuration. test/setup.ts
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import homeES from '#/public/locales/es/home.json';
import homeEN from '#/public/locales/en/home.json';
i18n.use(initReactI18next).init({
lng: "es",
resources: {
en: {
home: homeEN,
},
es: {
home: homeES,
}
},
fallbackLng: "es",
debug: false,
});
export default i18n;
Then we add the setup file to our jest.config.js:
setupFilesAfterEnv: ["<rootDir>/test/setup.ts"]
Now we can try our tests using the I18nextProvider and the useTranslation hook:
import '#testing-library/jest-dom/extend-expect';
import { cleanup, render, renderHook } from '#testing-library/react';
import { act } from 'react-dom/test-utils';
import { I18nextProvider, useTranslation } from 'react-i18next';
import Home from '.';
describe("Index page", (): void => {
afterEach(cleanup);
it("should render properly in Spanish", (): void => {
const t = renderHook(() => useTranslation());
const component = render(
<I18nextProvider i18n={t.result.current.i18n}>
<Home / >
</I18nextProvider>
);
expect(component.getByText("Bienvenido a Pocky")).toBeInTheDocument();
});
it("should render properly in English", (): void => {
const t = renderHook(() => useTranslation());
act(() => {
t.result.current.i18n.changeLanguage("en");
});
const component = render(
<I18nextProvider i18n={t.result.current.i18n}>
<Home/>
</I18nextProvider>
);
expect(component.getByText("Welcome to Pocky")).toBeInTheDocument();
});
});
Here we used the I18nextProvider and send the i18n instance using the useTranslation hook. after that the translations were loaded without problems in the Home component.
We can also change the selected language running the changeLanguage() function and test the other translations.

GSAP timeline needed on every page in Gatsby

My Gatsby site use the same GSAP timeline on every page, so I want to stay DRY and my idea is to include my timeline in my Layout component in that order.
But I don't know how to pass refs that I need between children and layout using forwardRef.
In short, I don't know how to handle the sectionsRef part between pages and layout.
sectionsRef is dependant of the page content (children) but is needed in the timeline living in layout.
How can I share sectionsRef between these two (I tried many things but always leading to errors)?
Here's a codesandbox without the refs in the Layout:
https://codesandbox.io/s/jolly-almeida-njt2e?file=/src/pages/index.js
And the sandbox with the refs in the layout:
https://codesandbox.io/s/pensive-varahamihira-tc45m?file=/src/pages/index.js
Here's a simplified version of my files :
Layout.js
export default function Layout({ children }) {
const containerRef = useRef(null);
const sectionsRef = useRef([]);
sectionsRef.current = [];
useEffect(() => {
gsap.registerPlugin(ScrollTrigger);
const scrollTimeline = gsap.timeline();
scrollTimeline.to(sectionsRef.current, {
x: () =>
`${-(
containerRef.current.scrollWidth -
document.documentElement.clientWidth
)}px`,
ease: 'none',
scrollTrigger: {
trigger: containerRef.current,
invalidateOnRefresh: true,
scrub: 0.5,
pin: true,
start: () => `top top`,
end: () =>
`+=${
containerRef.current.scrollWidth -
document.documentElement.clientWidth
}`,
},
});
}, [containerRef, sectionsRef]);
return (
<div className="slides-container" ref={containerRef}>
{children}
</div>
);
}
index.js (page)
import { graphql } from 'gatsby';
import React, { forwardRef } from 'react';
import SectionImage from '../components/sections/SectionImage';
import SectionIntro from '../components/sections/SectionIntro';
import SectionColumns from '../components/sections/SectionColumns';
const HomePage = ({ data: { home } }, sectionsRef) => {
const { sections } = home;
const addToRefs = (el) => {
if (el && !sectionsRef.current.includes(el)) {
sectionsRef.current.push(el);
}
};
return (
<>
{sections.map((section) => {
if (section.__typename === 'SanitySectionIntro') {
return (
<SectionIntro key={section.id} section={section} ref={addToRefs} />
);
}
if (section.__typename === 'SanitySectionImage') {
return (
<SectionImage key={section.id} section={section} ref={addToRefs} />
);
}
if (section.__typename === 'SanitySectionColumns') {
return (
<SectionColumns
key={section.id}
section={section}
ref={addToRefs}
/>
);
}
return '';
})}
</>
);
};
export default forwardRef(HomePage);
export const query = graphql`
query HomeQuery {
// ...
}
`;
Any clue greatly appreciated :)

How to get the links in Draft js in read only mode?

I am creating a simple blog writing application. I am using Draft.js as an editor. I am able to create the link while writing the blog but when I go into read mode all the links are missing. Here are the React code for writing and reading the blogs. For simplicity I am storing the editorState/data in localStorage. Here is WriteBlog.js file
import React, { Component } from "react";
import Editor, { createEditorStateWithText } from "draft-js-plugins-editor";
import createInlineToolbarPlugin from "draft-js-inline-toolbar-plugin";
import createLinkPlugin from "draft-js-anchor-plugin";
import createToolbarPlugin, { Separator } from "draft-js-static-toolbar-plugin";
import {
convertFromRaw,
EditorState,
RichUtils,
AtomicBlockUtils,
convertToRaw
} from "draft-js";
import { ItalicButton, BoldButton, UnderlineButton } from "draft-js-buttons";
import editorStyles from "./editorStyles.css";
import buttonStyles from "./buttonStyles.css";
import toolbarStyles from "./toolbarStyles.css";
import linkStyles from "./linkStyles.css";
import "draft-js-alignment-plugin/lib/plugin.css";
const staticToolbarPlugin = createToolbarPlugin();
const linkPlugin = createLinkPlugin({
theme: linkStyles,
placeholder: "http://…"
});
const inlineToolbarPlugin = createInlineToolbarPlugin({
theme: { buttonStyles, toolbarStyles }
});
const { Toolbar } = staticToolbarPlugin;
const { InlineToolbar } = inlineToolbarPlugin;
const plugins = [staticToolbarPlugin, linkPlugin];
const text =
"Try selecting a part of this text and click on the link button in the toolbar that appears …";
export default class WriteBlog extends Component {
state = {
editorState: createEditorStateWithText(text)
};
onChange = editorState => {
let contentRaw = convertToRaw(this.state.editorState.getCurrentContent());
const stringyfyRawContent = JSON.stringify(contentRaw);
localStorage.setItem("blogcontent", JSON.stringify(contentRaw));
this.setState({
editorState
});
};
handleSave = async e => {
e.preventDefault();
// await this.setState({
// saveblog: 1,
// publish: 0
// });
// this.props.create_post(this.state);
// console.log("save state", this.state);
localStorage.setItem(
"blogsaveblog",
JSON.stringify(this.state.editorState)
);
};
focus = () => this.editor.focus();
render() {
return (
<div className={editorStyles.editor} onClick={this.focus}>
<form onSubmit={this.handleSave}>
<Editor
editorState={this.state.editorState}
onChange={this.onChange}
plugins={plugins}
ref={element => {
this.editor = element;
}}
/>
<Toolbar>
{// may be use React.Fragment instead of div to improve perfomance after React 16
externalProps => (
<div>
<BoldButton {...externalProps} />
<ItalicButton {...externalProps} />
<UnderlineButton {...externalProps} />
<linkPlugin.LinkButton {...externalProps} />
</div>
)}
</Toolbar>
<button
type="submit"
className="btn btn-primary"
style={{ margin: "10px" }}
>
Save
</button>
</form>
</div>
);
}
}
and here is ReadBlog.js file
import React, { Component } from "react";
import Editor, { createEditorStateWithText } from "draft-js-plugins-editor";
import createInlineToolbarPlugin from "draft-js-inline-toolbar-plugin";
import createLinkPlugin from "draft-js-anchor-plugin";
import createToolbarPlugin, { Separator } from "draft-js-static-toolbar-plugin";
import { convertFromRaw, EditorState, convertToRaw } from "draft-js";
import { ItalicButton, BoldButton, UnderlineButton } from "draft-js-buttons";
import editorStyles from "./editorStyles.css";
import buttonStyles from "./buttonStyles.css";
import toolbarStyles from "./toolbarStyles.css";
import linkStyles from "./linkStyles.css";
import "draft-js-alignment-plugin/lib/plugin.css";
const staticToolbarPlugin = createToolbarPlugin();
const linkPlugin = createLinkPlugin({
theme: linkStyles,
placeholder: "http://…"
});
const inlineToolbarPlugin = createInlineToolbarPlugin({
theme: { buttonStyles, toolbarStyles }
});
const { Toolbar } = staticToolbarPlugin;
const { InlineToolbar } = inlineToolbarPlugin;
const plugins = [staticToolbarPlugin, linkPlugin];
const text =
"Try selecting a part of this text and click on the link button in the toolbar that appears …";
export default class ReadBlog extends Component {
state = {
editorState: createEditorStateWithText(text)
};
componentDidMount = () => {
const rawContentFromdb = convertFromRaw(
JSON.parse(localStorage.getItem("blogcontent"))
);
const initialEditorStatedb = EditorState.createWithContent(
rawContentFromdb
);
this.setState({ editorState: initialEditorStatedb });
};
focus = () => this.editor.focus();
render() {
return (
<div className={editorStyles.editor} onClick={this.focus}>
<Editor
editorState={this.state.editorState}
plugins={plugins}
readOnly={true}
ref={element => {
this.editor = element;
}}
/>
</div>
);
}
}
I know this is super late, but you are not adding the decorator which is why this is not working. In this case, you'll want to use compositeDecorator to build your decorator object, and initialize the react state with it.
const decorator = new CompositeDecorator([
{
strategy: linkStrategy,
component: LinkDecorator,
},
]);
const [editorState, setEditorState] = useState(() =>
EditorState.createWithContent(initialContentState, decorator),
);

Resources