App
import React from 'react'
import MyComponent from './MyComponent'
import {ConnectionProvider from './context'
const App extends React.Component {
render() {
return (
<ConnectionProvider>
<MyComponent />
</ConnectionProvider>
)
}
}
export default App
Context
import React, { createContext, useState } from 'react'
import { Connection } from '#web/_types'
import { ContactService } from '#web/_services'
interface IConnectionContextData {
connections: Connection[]
getConnections: () => void
connect: (
contactId: string,
source: string,
) => void
}
const ConnectionContextDefaultValue: IConnectionContextData = {
connections: []
getConnections: () => null,
connect: () => null,
}
const useConnectionContextValue = (): ConnectionContextData => {
const [connections, setConnections] = useState<Connection[]>([])
const connect = async (
contactId: string,
source: string
) => {
try {
await ContactService.connectRep(contactId, source)
getConnections()
} catch (error) {
console.log(error)
}
}
const getConnections = async () => {
const getContactsResponse = await UserService.getConnections()
setConnections(getContactsResponse.data)
}
return {
getConnections,
connect,
}
}
export const ConnectionContext = createContext<ConnectionContextData>(
ConnectionContextDefaultValue
)
export const ConnectionProvider: React.FC = ({ children }) => {
return (
<ConnectionContext.Provider value={useConnectionContextValue()}>
{children}
</ConnectionContext.Provider>
)
}
MyComponent
import React, {useContext} from 'react'
import {ConnectionContext} from './context'
const MyComponent extends React.Component {
const { connect } = useContext(ConnectionContext)
render() {
return (
<>
<button onClick={() => connect('1234', 'Some source')}>
Click me
</button>
</>
)
}
}
export default MyComponent
Hopefully, this is enough context (pardon the sh!tty pun).
I'm trying to figure out how to test that ContactService.connectRep was called with contactId and source. Everything I've read says to avoid testing context directly, but I can only mock up connect as jest.fn().
I've mocked the context provider, and I can see that the connect function was called with contactId and source. That all passes just fine, but I'm not sure how to approach knowing how ContactService.connectRep was called. Any ideas?
Related
I want to know if is possible to navigate between screens, using like a context api, or something else, where I can get the "navigateTo" function in any component without passing by props. And of course, without the cycle dependency problem.
Example with the cycle dependency problem
NavigateContext.tsx:
import React, { createContext, useMemo, useReducer } from 'react'
import { Home } from './pages/Home'
interface NavigateProps {
navigateTo: (screenName: string) => void
}
export const navigateContext = createContext({} as NavigateProps)
const reducer = (state: () => JSX.Element, action: { type: string }) => {
switch (action.type) {
case 'home':
return Home
default:
throw new Error('Page not found')
}
}
export function NavigateContextProvider() {
const [Screen, dispatch] = useReducer(reducer, Home)
const value = useMemo(() => {
return {
navigateTo: (screenName: string) => {
dispatch({ type: screenName })
},
}
}, [])
return (
<navigateContext.Provider value={value}>
<Screen />
</navigateContext.Provider>
)
}
Home.tsx:
import React, { useContext, useEffect } from 'react'
import { Flex, Text } from '#chakra-ui/react'
import { navigateContext } from '../NavigateContext'
export function Home() {
const { navigateTo } = useContext(navigateContext)
useEffect(() => {
setTimeout(() => {
navigateTo('home')
}, 2000)
}, [])
return (
<Flex>
<Text>Home</Text>
</Flex>
)
}
Yes, this is possible, but you'll need to maintain the list of string view names independently from your mapping of them to their associated components in order to avoid circular dependencies (what you call "the cycle dependency problem" in your question):
Note, I created this in the TS Playground (which doesn't support modules AFAIK), so I annotated module names in comments. You can separate them into individual files to test/experiment.
TS Playground
import {
default as React,
createContext,
useContext,
useEffect,
useState,
type Dispatch,
type ReactElement,
type SetStateAction,
} from 'react';
////////// views.ts
// Every time you add/remove a view in your app, you'll need to update this array:
export const views = ['home', 'about'] as const;
export type View = typeof views[number];
export type ViewContext = {
setView: Dispatch<SetStateAction<View>>;
};
export const viewContext = createContext({} as ViewContext);
////////// Home.ts
// import { viewContext } from './views';
export function Home (): ReactElement {
const {setView} = useContext(viewContext);
useEffect(() => void setTimeout(() => setView('home'), 2000), []);
return (<div>Home</div>);
}
////////// About.ts
// import { viewContext } from './views';
export function About (): ReactElement {
const {setView} = useContext(viewContext);
return (
<div>
<div>About</div>
<button onClick={() => setView('home')}>Go Home</button>
</div>
);
}
////////// ContextProvider.tsx
// import {viewContext, type View} from './views';
// import {Home} from './Home';
// import {About} from './About';
// import {Etc} from './Etc';
// Every time you add/remove a view in your app, you'll need to update this object:
const viewMap: Record<View, () => ReactElement> = {
home: Home,
about: About,
// etc: Etc,
};
function ViewProvider () {
const [view, setView] = useState<View>('home');
const CurrentView = viewMap[view];
return (
<viewContext.Provider value={{setView}}>
<CurrentView />
</viewContext.Provider>
);
}
I want add screen loading in next js project. And I tried to do that with the Router component in next/router.
This is my _app.js in next.js project:
import {CookiesProvider} from 'react-cookie';
import App from 'next/app'
import React from 'react'
import {Provider} from 'react-redux'
import withRedux from 'next-redux-wrapper'
import withReduxSaga from 'next-redux-saga'
import createStore from '../src/redux/store'
import Router from "next/router";
import {Loaded, Loading} from "../src/util/Utils";
class MyApp extends App {
static async getInitialProps({Component, ctx}) {
let pageProps = {};
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps({ctx})
}
return {pageProps}
}
render() {
Router.onRouteChangeStart = () => {
Loading()
};
Router.onRouteChangeComplete = () => {
Loaded()
};
Router.onRouteChangeError = () => {
Loaded()
};
const {Component, pageProps, store} = this.props;
return (
<CookiesProvider>
<Provider store={store}>
<Component {...pageProps} />
</Provider>
</CookiesProvider>
)
}
}
export default withRedux(createStore)(withReduxSaga(MyApp))
This is Loaded() and Loading() functions:
export const Loaded = () => {
setTimeout(() => {
let loading = 'has-loading';
document.body.classList.remove(loading);
}, 100);
};
export const Loading = () => {
let loading = 'has-loading';
document.body.classList.add(loading);
};
The code works well when the project is under development mode. But when the project is built, the loading won't disappear.
Do you know solution of this issue or are you suggesting another solution?
Using apollo client and react hooks you could do as follow.
Example:
import { useQuery } from '#apollo/react-hooks';
import gql from 'graphql-tag';
import { withApollo } from '../lib/apollo';
import UserCard from '../components/UserCard';
export const USER_INFO_QUERY = gql`
query getUser ($login: String!) {
user(login: $login) {
name
bio
avatarUrl
url
}
}
`;
const Index = () => {
const { query } = useRouter();
const { login = 'default' } = query;
const { loading, error, data } = useQuery(USER_INFO_QUERY, {
variables: { login },
});
if (loading) return 'Loading...'; // Loading component
if (error) return `Error! ${error.message}`; // Error component
const { user } = data;
return (
<UserCard
float
href={user.url}
headerImg="example.jpg"
avatarImg={user.avatarUrl}
name={user.name}
bio={user.bio}
/>
);
};
export default withApollo({ ssr: true })(Index);
More info here: https://github.com/zeit/next.js/tree/canary/examples/with-apollo
I added the following codes to a wrapper component and the problem was resolved.
componentDidMount() {
Loaded();
}
componentWillUnmount() {
Loading();
}
I want my function below, "VerifyEmailResend" to have access to the two dispatch methods inside of mapDispatchToProps. How do I do that?
VerifyEmailResend() is exported because i want it available to be called throughout my application.
This app is written in React using Redux.
I know normally the connect method is used, but connect is for react components specifically. Is there something similar here I am missing?
import React, { Component, Fragment } from 'react'
import { api } from '../../api'
import { bool, func } from 'prop-types'
import VerifyEmailResent from './VerifyEmailResent'
import VerifyEmailVerified from './VerifyEmailVerified'
import { connect } from 'react-redux'
class VerifyEmail extends Component {
static propTypes = {
// has the verify email been resent \\
verifyEmailResent: bool,
// have you already verified your email \\
verifyEmailVerified: bool,
// set status of verify email \\
onSetVerifyEmailResent: func.isRequired,
// set status of the email verified \\
onSetVerifyEmailVerified: func.isRequired
}
resent = () => {
const { onSetVerifyEmailResent } = this.props
onSetVerifyEmailResent(false)
}
verified = () => {
const { onSetVerifyEmailVerified } = this.props
onSetVerifyEmailVerified(false)
}
render() {
const { verifyEmailResent, verifyEmailVerified } = this.props
return (
<Fragment>
{verifyEmailResent && (
<VerifyEmailResent action={this.resent} />
)}
{verifyEmailVerified && (
<VerifyEmailVerified action={this.verified} />
)}
</Fragment>
)
}
}
const mapStateToProps = state => ({
verifyEmailResent: state.eventListenerState.verifyEmailResent,
verifyEmailVerified: state.eventListenerState.verifyEmailResent
})
const mapDispatchToProps = dispatch => ({
onSetVerifyEmailResent: verifyEmailResent =>
dispatch({ type: 'VERIFY_EMAIL_RESENT_SET', verifyEmailResent }),
onSetVerifyEmailVerified: verifyEmailVerified =>
dispatch({ type: 'VERIFY_EMAIL_RESENT_VERIFIED', verifyEmailVerified })
})
const VerifyEmailResend = () => () => {
api.user.resendEmailVerification().then(data => {
if (data.resent) {
//onSetVerifyEmailResent(true)
}
if (data.verified) {
//onSetVerifyEmailVerified(false)
}
})
}
export connect(null, mapDispatchToProps)(VerifyEmailResend)
export default connect(
mapStateToProps,
mapDispatchToProps
)(VerifyEmail)
I am not sure what you're confused at. But you can do it:
const VerifyEmailResend = () => {}
export default connect(null, mapDispatchToProps)(VerifyEmailResend)
As per you need a named export, you can do it like:
export {
VerifyEmailResend: connect(null, mapDispatchToProps)(VerifyEmailResend)
}
And you can import it normally like:
import { VerifyEmailResend } from '..'
And as per your comment, you can call it like followings depending your need of field:
{ VerifyEmailResend() }
Or like:
<VerifyEmailResend />
I wrote here is the code
import React, { FC, Fragment, useEffect } from "react";
import { createBrowserHistory } from "history";
const history = createBrowserHistory();
const HandlerErr: FC<{ error: string }> = ({ error }) => {
useEffect(()=>{
const time = setTimeout(() => {history.push(`/`)}, 2000);
return(()=> clearTimeout(time));
},[error])
return (
<Fragment>
<div>{error}</div>
<div>{"Contact site administrator"}</div>
</Fragment>
);
};
I use the HandlerErr component to redirect. but for some reason it doesn't work history.push (/).I took a video
You need to use history form the react-router-dom
like
import React, { Component } from 'react'
import { withRouter } from 'react-router-dom'
class Test extends Component {
render () {
const { history } = this.props
return (
<div>
<Button onClick={() => history.push('./path')}
</div>
)
}
}
export default withRouter(Test)
import React, { FC, useEffect } from 'react';
import { RouteComponentProps, withRouter } from 'react-router-dom';
interface OwnProps {
error: string;
}
type Props = OwnProps & RouteComponentProps<any>;
const HandlerErr: FC<Props> = ({ error, history }) => {
useEffect(() => {
const timeout = setTimeout(() => {
history.push(`/`);
}, 2000);
return () => {
clearTimeout(timeout);
};
}, [error]);
return (
<>
<div>{error}</div>
<div>Contact site administrator</div>
</>
);
};
export default withRouter(HandlerErr);
Say I have the following wrapper component:
'use strict'
import React, {PropTypes, PureComponent} from 'react'
import {update} from '../../actions/actions'
import LoadFromServerButton from '../LoadFromServerButton'
import {connect} from 'react-redux'
export class FooDisplay extends PureComponent {
render () {
return (
<p>
<span className='foo'>
{this.props.foo}
</span>
<LoadFromServerButton updateFunc={this.props.update} />
</p>
)
}
}
export const mapStateToProps = (state) => {
return {foo: state.foo.foo}
}
FooDisplay.propTypes = {
foo: PropTypes.string
}
export const mapDispatchToProps = (dispatch) => {
return {
update: (foo) => dispatch(update(foo))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(FooDisplay)
and the following inner component:
'use strict'
import React, {PropTypes, PureComponent} from 'react'
import {get} from '../../actions/actions'
import ActiveButton from '../ActiveButton'
import {connect} from 'react-redux'
export class LoadFromServerButton extends PureComponent {
doUpdate () {
return this.props.get().then(this.props.updateFunc)
}
render () {
return (
<ActiveButton action={this.doUpdate.bind(this)} actionArguments={[this.props.foo]} text='fetch serverside address' />
)
}
}
export const mapStateToProps = (state) => {
return {foo: state.foo.foo}
}
export const mapDispatchToProps = (dispatch) => {
return {
get: () => dispatch(get())
}
}
LoadAddressFromServerButton.propTypes = {
updateFunc: PropTypes.func.isRequired
}
export default connect(mapStateToProps, mapDispatchToProps)(LoadFromServerButton)
ActiveButton is a very thin wrapper around a button with an onclick and arguments destructuring.
Now lets say that I my get action is written as follows:
export const get = () => dispatch => http('/dummy_route')
.spread((response, body) => dispatch(actOnThing(update, body)))
Now if I write a test like so:
/* global window, test, expect, beforeAll, afterAll, describe */
'use strict'
import React from 'react'
import FooDisplay from './index'
import {mount} from 'enzyme'
import {Provider} from 'react-redux'
import configureStore from '../../store/configureStore'
import nock, {uriString} from '../../config/nock'
import _ from 'lodash'
const env = _.cloneDeep(process.env)
describe('the component behaves correctly when integrating with store and reducers/http', () => {
beforeAll(() => {
nock.disableNetConnect()
process.env.API_URL = uriString
})
afterAll(() => {
process.env = _.cloneDeep(env)
nock.enableNetConnect()
nock.cleanAll()
})
test('when deep rendering, the load event populates the input correctly', () => {
const store = configureStore({
address: {
address: 'foo'
}
})
const display = mount(<Provider store={store}><FooDisplay /></Provider>,
{attachTo: document.getElementById('root')})
expect(display.find('p').find('.address').text()).toEqual('foo')
const button = display.find('LoadFromServerButton')
expect(button.text()).toEqual('fetch serverside address')
nock.get('/dummy_address').reply(200, {address: 'new address'})
button.simulate('click')
})
})
This results in:
Unhandled rejection Error: Error: connect ECONNREFUSED 127.0.0.1:8080
After a little bit of thinking, this is due to the fact that the test does not return a promise, as the button click causes the promise to fire under the hood, therefore, afterAll runs immediatly, cleans nock, and a real http connection goes over the wire.
How do I test this case? I don't seem to have an easy way to return the correct promise... How do I test updates to the DOM resulting from these updates?
In order to mock only one method of the imported module, use .requireActual(...)
jest.mock('../your_module', () => ({
...(jest.requireActual('../your_module')),
YourMethodName: () => { return { type: 'MOCKED_ACTION'}; }
}));
As you mentioned the problem is that you dont have the promise to return from test. So to make get return a know promise you can just mock get directly without using nock:
import {get} from '../../actions/actions'
jest.mock('../../actions/actions', () => ({get: jest.fn}))
this will replace the action module with an object {get: jestSpy}
in your test you can then create a promise and let get return this and also return this promise from your test:
it('', ()=>{
const p = new Promise.resolve('success')
get.mockImplementation(() => p)//let get return the resolved promise
//test you suff
return p
})