What should I expect for navigate buttons in react using react-testing library - react-hooks

I don't understand what should I expect for navigate button in the below code. can any one help me with this. Thank you.
code:
import react from 'react';
const HomeButton = (props) => {
const history = props.history;
function handleClick() {
history.push("/home");
}
return (
<button type="button" onClick={handleClick} data-testid="goToHome">
Go home
</button>
);
}
export default HomeButton;
This is the test code I have been trying for the above component
import React from 'react';
import { render, screen, fireEvent } from '#testing-library/react';
import GoToHome from '../GoToHome';
describe('Read only text', () => {
const history = createMemoryHistory();
it('text came from props', () => {
const { container } = render(<GoToHome history={history} />);
const goToHome = screen.getByTestId('goToHome')
fireEvent.click(goToHome, jest.fn())
expect(container).
});
});

you can check my passing the history as a prop with push property assigned to jest.fn as mock function and check if it is getting called when you press to navigate button
describe('Read only text', () => {
const mockPush = jest.fn()
const history = {
push: mockPush()
}
it('text came from props', () => {
const { container } = render(<GoToHome history={history} />);
const goToHome = screen.getByTestId('goToHome')
fireEvent.click(goToHome)
expect(mockPush ).toBeCalled()
});
});

Related

Why need setState func Inside useEffect func on this code

import { useRef, useEffect, useState } from 'react'
import { createPortal } from 'react-dom'
import styled from 'styled-components'
/** HeaderModal custom hooks */
const useHeaderModal = () => {
const [modalOpened, setModalOpened] = useState(false)
const openModal = () => {
setModalOpened(true)
}
const closeModal = () => {
setModalOpened(false)
}
interface IProps {
children: React.ReactNode
}
const ModalPortal: React.FC<IProps> = ({ children }) => {
const ref = useRef<Element | null>()
const [mounted, setMounted] = useState(false)
useEffect(() => {
setMounted(true)
if (document) {
const dom = document.querySelector('#root-modal')
ref.current = dom
}
}, [])
if (ref.current && mounted && modalOpened) {
return createPortal(
<Container>
<div
className="modal-background"
role="presentation"
onClick={closeModal}
></div>
{children}
</Container>,
ref.current,
)
}
return null
}
return {
openModal,
closeModal,
ModalPortal,
}
}
export default useHeaderModal
This code is custom hook for opening modal.
And I don't understand 'Why need setState func Inside useEffect func on this code?'.
When I eliminate
setMounted(true), ref was not created.
And then in case clicking modal open button
ref was not much created.
When the button is pressed only when the setMounted function
is put in useEffect
The ref is created and then the modal is opened.
Could you please tell me why?

Why calling fit() is rezing the window wrongly?

I'm getting this resize error when I get out the tab then get back:
I did notice that this happens when I call FitAddon.Fit() inside my ResizeObserver. I was using onSize to call .fit() but I did notice that when I used <Resizable>, the onSize event didn't fire so I went to use ResizeObserver and called .fit() from ResizeObserver's callback so the resize error begun.
my code look like this:
import { useEffect, useRef } from "react";
import { Terminal as TerminalType } from 'xterm';
import { FitAddon } from 'xterm-addon-fit';
import { WebLinksAddon } from 'xterm-addon-web-links';
import 'xterm/css/xterm.css';
export const Terminal = () => {
const id = 'xterm-container';
useEffect(() => {
const container = document.getElementById(id) as HTMLElement;
const terminal = new TerminalType({
cursorBlink: true,
cursorStyle: window.api.isWindows ? "bar" : "underline",
cols: DefaultTerminalSize.cols,
rows: DefaultTerminalSize.rows
});
const fitAddon = new FitAddon();
terminal.loadAddon(fitAddon);
terminal.loadAddon(new WebLinksAddon());
const resizeObserver = new ResizeObserver(entries => {
fitAddon.fit();
});
resizeObserver.observe(container);
terminal.open(container);
terminal.onData(key => {
window.api.send('terminal.data', key);
});
terminal.onResize(size => {
window.api.send('terminal.resize', {
cols: size.cols,
rows: size.rows
});
});
fitAddon.fit();
return () => {
resizeObserver.unobserve(container);
}
}, []);
return (
<div
id={id}
style={{height: '100%',
width: '100%',
}}
>
</div>
)
}

Why my reducer doesn't work when I click a button?

So I've just started learning redux, and I created a simple reducer which adds a number when you click on the button.
It looks like this:
const defaultState = {
cash: 5,
}
export const cashReducer = (state = defaultState, action) =>{
switch(action.type){
case 'ADD_CASHER':
return{...state, cash: state.cash + action.payload}
case 'GET_CASHER':
return{...state, cash: state.cash - action.payload}
default:
return state
}
}
index.js file:
import { createStore, combineReducers } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import { cashReducer } from './cashReducer';
import { customReducer} from './customReducer';
const rootReducer = combineReducers({
cash: cashReducer,
custom: customReducer,
})
export const store = createStore(rootReducer, composeWithDevTools());
App.js file:
const App = () => {
const dispatch = useDispatch();
const cash = useSelector(state => state.cash.cash);
const addCash = (cash) =>{
dispatch({type: 'ADD_CASH', payload: cash});
}
const getCash = (cash) =>{
dispatch({type: 'GET_CASH', payload: cash});
}
return(
<div className="main">
<h1 style={{textAlign:'center'}}>{cash}</h1>
<button className="btn" onClick={() => addCash(Number(prompt()))}>add cash</button>
<button className="btn" onClick={() => getCash(Number(prompt()))}>get cash</button>
</div>
)
}
And buttons look like this:
So when I click on the button it should receive a number through the prompt and add it to the current number. But when I click both of the buttons nothing happens.
Everything is pretty simple, so I can't figure out what could be the problem here.

I'm trying to Use AppLoading to render home screen components but cant get the splash screen to stay until everything is loaded

After optimising my images iv realised that I still need more time for my components to load. They are card like components with images.
I have 2 components to load one is in a flatList, the other just a basic card like component each component contains images. I have been trying in vain to get this to work and have to ask if anyone has a good solution. Here's what I have so far.
import React, { useState } from "react";
import { View, StyleSheet } from "react-native";
import AppLoading from "expo-app-loading";
import Header from "./components/Header";
import HomeScreen from "./screens/HomeScreen";
const fetchHomeScreen = () => {
return HomeScreen.loadAsync({
HomeScreen: require("./screens/HomeScreen"),
Header: require("./components/Header"),
});
};
export default function App() {
const [HomeScreenLoaded, setHomeScreenLoaded] = useState(false);
if (!HomeScreenLoaded) {
return (
<AppLoading
startAsync={fetchHomeScreen}
onFinish={() => setHomeScreenLoaded(true)}
onError={(err) => console.log(err)}
/>
);
}
return (
<View style={styles.screen}>
<Header title="Your Beast Log" />
<HomeScreen />
</View>
);
}
const styles = StyleSheet.create({
screen: {
flex: 1,
backgroundColor: "#3E3636",
},
});
Are you using expo status bar ?? then try these;
import splash screen
import * as SplashScreen from 'expo-splash-screen';
add this to the top of main function
SplashScreen.preventAutoHideAsync()
export default function App() {....}
then add this to your screen where you hide the splash screen
const [appIsReady, setAppIsReady] = useState(false);
useEffect(() => {
if(appIsready) {
(async () => {
await SplashScreen.hideAsync();
})()
}
},[appIsReady])
async function prepare() {
try {
await fetchHomeScreen;
//OR
await HomeScreen.loadAsync({
HomeScreen: require("./screens/HomeScreen"),
Header: require("./components/Header"),
});
} catch (e) {
console.warn(e);
} finally {
// Tell the application to render
setAppIsReady(true);
}
}

[updated]Intergrating NextJS and Redux State Management

this my updated version of intergrating redux and NextJS. Just to elobarate what I have done so far...
STEP 1. I've created a store.js file to set up my global store in reference to github's explanation from nextJS developers.
import { createStore, applyMiddleware, combineReducers } from 'redux';
import { createWrapper, HYDRATE } from 'next-redux-wrapper';
import thunkMiddleware from 'redux-thunk';
import { customerListReducer } from './customerReducers';
const bindMiddleware = (middleware) => {
if (process.env.NODE_ENV !== 'production') {
const { composeWithDevTools } = require('redux-devtools-extension');
return composeWithDevTools(applyMiddleware(...middleware));
}
return applyMiddleware(...middleware);
};
const combinedReducer = combineReducers({
customerList: customerListReducer,
});
const reducer = (state, action) => {
console.log('Just Displaying the Store', state);
if (action.type === HYDRATE) {
const nextState = {
...state, // use previous state
...action.payload, // apply delta from hydration
};
if (state.count) nextState.count = state.count; // preserve count value on client side navigation
return nextState;
} else {
return combinedReducer(state, action);
}
};
// create a makeStore function
const store = () =>
createStore(
reducer,
bindMiddleware([thunkMiddleware])
);
// export an assembled wrapper
export const wrapper = createWrapper(store);
STEP 2: Imported the wrapper above in my _app file to make the wrapper available across all pages in my application
import Nav from '../components/Nav';
import {wrapper} from '../reducers/store';
function MyApp({ Component, pageProps }) {
return (
<>
<Nav />
<Component {...pageProps} />
</>
);
}
export default wrapper.withRedux(MyApp);
STEP 3: CONFIGURATIONS
A) My Action that calls external API
import axios from 'axios';
import {
CUSTOMER_LIST_REQUEST,
CUSTOMER_LIST_SUCCESS,
CUSTOMER_LIST_FAIL,
} from '../constants/customerConstants';
export const listCustomers = () => async (dispatch) => {
try {
dispatch({
type: CUSTOMER_LIST_REQUEST,
});
const { data } = await axios.get(
'https://byronochara.tech/gassystem/api/v1/customers'
);
const result = data.results;
dispatch({
type: CUSTOMER_LIST_SUCCESS,
payload: result,
});
} catch (error) {
dispatch({
type: CUSTOMER_LIST_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
});
}
};
B)My Action Reducer
import {
CUSTOMER_LIST_REQUEST,
CUSTOMER_LIST_SUCCESS,
CUSTOMER_LIST_FAIL,
} from '../constants/customerConstants';
import { HYDRATE } from 'next-redux-wrapper';
export const customerListReducer = (state = { customers: [] }, action) => {
switch (action.type) {
case HYDRATE:
return { loading: true, customers: [] };
case CUSTOMER_LIST_REQUEST:
return { loading: true, customers: [] };
case CUSTOMER_LIST_SUCCESS:
return {
loading: false,
customers: action.payload,
};
case CUSTOMER_LIST_FAIL:
return { loading: false, error: action.payload };
default:
return state;
}
};
C)The finally bringing it all together in my index.js page to display the results:
import React, { useEffect } from 'react';
import Head from 'next/head';
import { useSelector} from 'react-redux';
import { listCustomers } from './../actions/customerActions';
import { wrapper } from '../reducers/store';
import styles from '../styles/Home.module.css';
const Home = () => {
//Select the loaded customers' list from central state
const customerList = useSelector((state) => {
console.log(state);
return state.customerList;
});
const { loading, error, customers } = customerList;
//displaying the customers data from the external API
console.log('Fetched Customers Data', customers);
return (
<div className={styles.container}>
<Head>
<title>Home | Next</title>
</Head>
<h1>Welcome to Home Page</h1>
{/* {loading && <h6>Loading...</h6>} */}
{/* {error && <h6>Error Occured...</h6>} */}
{/* {customers.map((customer) => (
<h3>{customer.customerName}</h3>
))} */}
{/* <ArticleList customers={customers} /> */}
</div>
);
};
// getStaticProp at build time
// getServerSideProp at every request slower
// getStaticPath to dynamically generate paths based on the data we are fetching
export const getStaticProps = wrapper.getServerSideProps(async ({ store }) => {
// console.log('STORE', store);
store.dispatch(listCustomers());
});
export default Home;
COMMENT ON THE PROBLEM I'M FACING FROM THE ABOVE CODE: once everything has been set up if you follow the code above, the code seems to run well the store is successfully created when I log the result on the console ``{ customerList: { loading: true, customers: [] } }. But then I guess this is the result from the HYDRATE action type since it will always be dispatch since am using getStaticProps``` that creates a new store instance in the server.
MAIN QUIZ: My challenge is how do I bypass the HYDRATED action and reconcile the server side state with the client side store and persist it and at least to finally be able to view the list from the external API. Thanks in advance. :)
I totally recommend you to use reduxjs/toolkit. It's very simple , less code, no wrappers, clean. And no matter your project on nextjs or created via CRA. Also you dont need to configure redux-thunk and redux-devtools cause they are enabled by default. Read documentation for more information ( how to persist state without any npm package and so on )
Here is a little example.
store.js
import { combineReducers, configureStore } from "#reduxjs/toolkit";
import userSlice from './user.slice.js';
//reducers
const rootReducer = combineReducers({
user: userSlice
});
const store = configureStore({
reducer: rootReducer,
});
export default store;
Wrap with Provider (in your case _app.js)
<Provider store={store}>
<Component {...pageProps} />
</Provider>
user.slice.js ( action + reducer )
import { createSlice, createAsyncThunk } from '#reduxjs/toolkit';
const initialState = {
id: '',
email: '',
roles: []
};
// export async action
export const signIn = createAsyncThunk('user/signIn', async (data) => {
try {
const payload = await api.auth.signin(data).then((res) => res.data);
// do some stuff if you want
return payload ;
} catch (err) {
console.log(err.response);
}
});
const userSlice = createSlice({
name: 'user',
initialState,
reducers: {
removeUser(state, payload) {
//cant be an async method
return initialState;
},
extraReducers: (builder) => {
builder.addCase(signIn.fulfilled, (state, { payload }) => {
// payload from the async method above (asyncThunk)
return payload;
});
},
},
});
// export actions
export const { removeUser } = userSlice.actions;
// export reducer
export default userSlice.reducer;
Thats it. Last step to call actions from any component e.g.
import { useDispatch, useSelector } from 'react-redux';
import { signIn, removeUser } from '../actions/userSlice';
// in function component
// call hooks
const dispatch = useDispatch();
// read the store
const { user } = useSelector((state) => state);
// dispatch any action , example below
dispatch(signIn(userCredentials));
// or
dispatch(removeUser());
I has an Issue with setting Redux with NextJS and this is my final answer after some insight from mirik999 too.
A. my store.
import { configureStore } from '#reduxjs/toolkit';
//importing the slice file with sliced reducers
import customerListReducer from '../slices/customerSlice';
// const composedEnhancer = composeWithDevTools(applyMiddleware(thunkMiddleware));
const store = configureStore({
reducer: {
customerList: customerListReducer,
},
});
export default store;
B. The store is provided in my app component
function MyApp({ Component, pageProps }) {
return (
<Provider store={store}>
<Nav />
<Component {...pageProps} />
</Provider>
);
}
export default MyApp;
C. The Slice file that automatically creates action creators and the reducer
import { createSlice } from '#reduxjs/toolkit';
//creating and action that calls API from a REST API backend
export const customersFetchedList = createAsyncThunk(
'customersList/customersListSuccess',
async () => {
try {
const { data } = await axios.get(
'https://example.com/api/your/endpoint'
);
const result = data.results;
//the payload
const payload = result;
return payload;
} catch (error) {
console.log(error.response);
const payload =
error.response && error.response.data.message
? error.response.data.message
: error.message;
return payload;
}
}
);
const initialState = {
loading: true,
customers: [],
error: false,
};
const customerListSlice = createSlice({
name: 'customersList',
initialState,
reducers: {
//reducer functions we've provided
customersRequest(state, action) {
if (state.loading == true) {
return state;
}
},
},
extraReducers: (builder) => {
initialState,
builder.addCase(customersFetchedList.fulfilled, (state, action) => {
state.loading = false;
state.customers = action.payload;
state.error = false;
return state;
});
},
});
export const {
customersRequest,
customersLoadingError,
} = customerListSlice.actions;
export default customerListSlice.reducer;
D. Then finally fired this action above in my component using the useEffect()
import React, { useEffect } from 'react';
import Head from 'next/head';
const Home = () => {
//method to fire the action
const dispatch = useDispatch();
//Select the loaded customers' list from central state
const customerList = useSelector((state) => state);
// const { loading, error, customers } = customerList;
useEffect(() => {
dispatch(listCustomers());
}, []);
return (
<div className={styles.container}>
<Head>
<title>Home | Next</title>
</Head>
<h1>Welcome to Home Page</h1>
{loading && <h6>Loading...</h6>}
{error && <h6>Error Occured...</h6>}
{customers.map((customer) => (
<h3>{customer.customerName}</h3>
))}
</div>
);
};
Thanks so much for your contribution. :)

Resources