On my component render, my useEffects hooks called, a function. the function updates the state status depending on the condition within the useEffects produce.
So in this case how to test the `mobileMenu` and how to set different condition in useEffect to test it?
I hope both my useEffects and useState need to mocked. I am in learning process with react. I could not get any correct answer upon searching, any one help me please?
here is my app.tsx
my ts file:
import { Footer, Header, ProductCart, ProductPhotoGallery, Tabs } from '#mcdayen/components';
import { Cart, Logo, MobileMenu, NaviLinks, QuickSearch, User } from '#mcdayen/micro-components';
import { initialNaviLinksProps, initialPhotoProps, initialTabsProps, NaviLinksProps, sizeProps } from '#mcdayen/prop-types';
import { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { fetchCartDetails, sizeHandler } from './store/cart.slice';
import { AppDispatch, RootState } from './store/store.config';
export function App() {
const dispatch:AppDispatch = useDispatch();
dispatch(fetchCartDetails());
const {product} = useSelector((state:RootState) => state.cartStore)
const [mobileMenu, setMobileMenu] = useState<boolean>(false);
const [linkProps, setLinkProps] = useState<NaviLinksProps | null>(null);
function mobileMenuHandler() {
setMobileMenu((current: boolean) => !current);
}
useEffect(() => {
setLinkProps(initialNaviLinksProps);
const mobileId = document.getElementById('mobileMenu');
if (mobileId?.offsetParent) {
mobileMenuHandler();
}
}, []);
useEffect(() => {
setLinkProps((props) => {
return mobileMenu ? { ...initialNaviLinksProps } : { ...initialNaviLinksProps, classProps: props?.classProps + ' hidden' }
})
}, [mobileMenu]);
function onSizeSelect(selectedSize: sizeProps) {
dispatch(sizeHandler(selectedSize));
}
return (
<section className="box-border m-auto flex flex-col pl-[18px] py-6 min-h-screen flex-wrap px-5 md:container md:w-[1440px] md:pl-[70px] pr-5 ">
<Header>
<Logo />
{linkProps && <NaviLinks passNaviLinks={linkProps} />}
<div className="flex gap-3">
<QuickSearch />
<Cart />
<User />
<MobileMenu menuHandler={mobileMenuHandler} />
</div>
</Header>
<main className='flex flex-col justify-between lg:flex-row'>
<div className='hidden lg:block w-[325px]'>
<div>
<Tabs tabProps={initialTabsProps} />
</div>
</div>
<div className='grow-0 flex-auto' >
{initialPhotoProps.length && <ProductPhotoGallery gallery={initialPhotoProps} />}
</div>
<div className='flex bg-white'>
{product && <ProductCart sizeSelect={onSizeSelect} passCartProps={product} />}
</div>
</main>
<Footer />
</section>
);
}
export default App;
My spec:
import { configureStore } from '#reduxjs/toolkit';
import { render } from '#testing-library/react';
import React from 'react';
import { Provider } from 'react-redux';
import { BrowserRouter } from 'react-router-dom';
import App from './app';
import cartReducer from './store/cart.slice';
jest.mock('react', () => ({
...jest.requireActual('react'),
useState: jest.fn(),
}));
export function createTestStore() {
const store = configureStore({
reducer: {
cartStore:cartReducer,
}
})
return store;
}
describe('App', () => {
const setMobileMenu = jest.fn();
const useStateMock = (initState: boolean) => [initState, setMobileMenu];
jest.spyOn(React, 'useState').mockImplementation(useStateMock);
afterEach(() => {
jest.clearAllMocks();
});
const store = createTestStore();
it('should render successfully', () => {
const { baseElement } = render(
<BrowserRouter>
<Provider store={store}>{<App />}</Provider>
</BrowserRouter>
);
expect(baseElement).toBeTruthy();
useStateMock(true);
expect(setMobileMenu).toHaveBeenCalledWith(true);
});
});
I am getting an error at: `
jest.spyOn(React, 'useState').mockImplementation(useStateMock);
`
as : Argument of type '(initState: boolean) => (boolean | jest.Mock<any, any>)[]' is not assignable to parameter of type '() => [unknown, Dispatch<unknown>]'.
and my test failing.
Need help for:
test the useEffect hook on anonymous function ( mocking )
fixing the error highlighted
testing the state on setMobileMenu
Any one please help me with the correct way?
Try to declare useStateMock as:
const useStateMock = (initState: any) => [initState, setMobileMenu];
Related
I am trying to use React Context successfully but I a have had a lot of trouble with it. I can't even successfully pass anything one level to the provider's immediate children, as a result all I am getting at this stage is "x is undefined" errors in the console. I am using a separate class for the context an a custom hook to manage my state data.
App.js (where TodoProvider component warps around its children) -
import logo from './logo.svg';
import './App.css';
import React, {createContext, useContext} from "react"
import TodoItem from './Components/Other/TodoItem';
import TodoList from './Components/List/TodoList';
import TodoAdd from './Components/Forms/TodoAdd';
import CompletedTaskList from './Components/List/CompletedTaskList';
import useTodo from './libs/useTodo';
import {TodoContext, TodoProvider} from "./Contexts/TodoContext"
function App() {
const {
todoArray, setTodoArray,
completedTaskArray,
addCompletedItem,
addTodoItem
} = useContext(TodoContext);
return (
<TodoProvider
value={
todoArray, setTodoArray,
completedTaskArray,
addCompletedItem,
addTodoItem
}
>
<div className="App">
<div className='card' id='mainCard'>
<div className='card-header' id='mainCardHeader'><h4>Todo List</h4></div>
<TodoList/>
<TodoAdd
/>
<CompletedTaskList
/>
</div>
</div>
</TodoProvider>
)
}
export default App;
TodoContext.js (My Context) -
import React, {createContext} from "react";
import useTodo from "../libs/useTodo";
const TodoContext = createContext();
const TodoProvider = ({children}) => {
const {
todoArray, setTodoArray,
completedTaskArray,
addCompletedItem,
addTodoItem
} = useTodo();
return (
<TodoContext.Provider
value={
todoArray, setTodoArray,
completedTaskArray,
addCompletedItem,
addTodoItem
}
>
{children}
</TodoContext.Provider>
)
}
export {TodoContext, TodoProvider}
useTodo.js (My custom hook to manage state)
import React, {useState} from "react"
const useTodo = () => {
const [todoArray, setTodoArray] = useState([{id: 1,todoTitle: "Code", todoDescription: "Practice React"},{id: 2,todoTitle: "Clean", todoDescription: "Wash dishes, wipe surfaces"}]);
const [completedTaskArray, setCompletedTaskArray] = useState(["Wake up", "Make Bed"]);
const [currentId, setCurrentId] = useState(3);
const addTodoItem = (todoTitleInputItem, todoDescriptionInputItem) => {
let todoTitle = todoTitleInputItem;
let todoDescription = todoDescriptionInputItem;
let id = currentId;
setCurrentId(currentId+1)
setTodoArray(todoArray => [...todoArray, {id,todoTitle, todoDescription}]);
}
const addCompletedItem = ({todoTitle}) => {
setCompletedTaskArray(completedTaskArray => [...completedTaskArray, todoTitle]);
}
return {
todoArray, setTodoArray,
completedTaskArray, setCompletedTaskArray,
addTodoItem,
addCompletedItem
}
}
export default useTodo;
CompletedTasklist(An example of my implementation of using a the context in one of it's children) -
import { useContext } from "react";
import {TodoContext, TodoProvider} from "../../Contexts/TodoContext"
const CompletedTaskList = () => {
const {
completedTaskArray
} = useContext(TodoContext);
return (
<div className="card todo-item">
<div className="card-header">
<h3> Completed Task</h3>
</div>
<div className="card-body">
<ul className="list-group ">
{completedTaskArray.map((item,index) => {
return <li className="list-group-item list-group-item-success" key={index}>{item}</li>
})}
</ul>
</div>
</div>
)
}
export default CompletedTaskList;
I've been trying to resolve this for a while now and cannot wrap my mind around it.
App.js
import React, { createContext, useContext } from 'react';
import CompletedTaskList from './comp';
import { TodoProvider } from './context';
function App() {
// you dont need useTodo, or TodoContext here
return (
<TodoProvider>
{/** todo Provider is a wrapper, you dont need to pass value as prop again, you are already doing it */}
<div className="App">
<div className="card" id="mainCard">
<div className="card-header" id="mainCardHeader">
<h4>Todo List</h4>
</div>
<CompletedTaskList />
</div>
</div>
</TodoProvider>
);
}
export default App;
Context
import React, { createContext } from 'react';
import useTodo from './useTodo';
// Define default values of your context data.
// otherwise everything would be undefined and you need to handle it everywhere
// you are using context
const TodoContext = createContext({
todoArray: [],
setTodoArray: () => {},
completedTaskArray: [],
addCompletedItem: () => {},
addTodoItem: () => {},
});
const TodoProvider = ({ children }) => {
const {
todoArray,
setTodoArray,
completedTaskArray,
addCompletedItem,
addTodoItem,
} = useTodo();
return (
<TodoContext.Provider
value={{
// <--- you were passing value incorrectly here, it should be an object
// you passed it as (......) instead of {...}
// curly braces not paranthesis
todoArray,
setTodoArray,
completedTaskArray,
addCompletedItem,
addTodoItem,
}}
>
{children}
</TodoContext.Provider>
);
};
export { TodoContext, TodoProvider };
Repasting the answer from the link i shared above.
I'm starting using React Testing Library to make tests for a React Application, but i'm struggling to mock the data in a component that makes API calls using a Hook as service.
My component is just a functional component, with nothing extraordinary:
import React, { useEffect, useRef, useState } from 'react'
import { useLocation } from 'react-router-dom'
import Skeleton from '#material-ui/lab/Skeleton'
import usePanelClient from '../../../clients/PanelClient/usePanelClient'
import useUtils from '../../../hooks/useUtils'
import IconFile from '../../../assets/img/panel/ico-file.svg'
import IconExclamation from '../../../assets/img/panel/ico-exclamation.svg'
import IconPause from '../../../assets/img/panel/awesome-pause-circle.svg'
import './PendingAwards.scss'
const PendingAwards = () => {
const pendingAwardsRef = useRef(null)
const location = useLocation()
const [loading, setLoading] = useState(true)
const [pendingAwards, setPendingAwards] = useState({})
const panelClient = usePanelClient()
const { formatCurrency } = useUtils()
useEffect(() => {
panelClient()
.getPendingAwards()
.then((response) => {
setLoading(false)
setPendingAwards(response.data)
})
}, [panelClient])
useEffect(() => {
const searchParams = new URLSearchParams(location.search)
if (searchParams.get('scrollTo') === 'pendingAwards') {
window.scrollTo({
top: 0,
behavior: 'smooth',
})
}
}, [location])
return (
<div
id="pendingAwards"
className="pending-awards-container"
ref={pendingAwardsRef}
>
<span className="pending-awards-container__title">Prêmios Pendentes</span>
{loading && (
<div className="skeleton-box">
<Skeleton width="100%" height="70px" />
<Skeleton width="100%" height="70px" />
</div>
)}
{!loading && (
<div className="pending-awards-values">
<div className="pending-awards-container__quantity">
<div className="pending-awards-container__quantity-container">
<span className="pending-awards-container__quantity-container-title">
Quantidade
</span>
<span className="pending-awards-container__quantity-container-content">
<div className="pending-awards-container__quantity-container-content__icon-box">
<img src={IconFile} alt="Ícone de arquivo" />
</div>
{pendingAwards.quantity ? pendingAwards.quantity : '0'}
</span>
</div>
</div>
<div className="pending-awards-container__amount">
<div className="pending-awards-container__amount-container">
<span className="pending-awards-container__amount-container-title">
Valor Pendente
</span>
<span className="pending-awards-container__amount-container-content">
<div className="pending-awards-container__amount-container-content__icon-box">
<img src={IconPause} alt="Ícone Pause" />
</div>
{pendingAwards.amount
? formatCurrency(pendingAwards.amount)
: 'R$ 0,00'}
</span>
</div>
</div>
<div className="pending-awards-container__commission">
<div className="pending-awards-container__commission-container">
<span className="pending-awards-container__commission-container-title">
Comissão Pendente
</span>
<span className="pending-awards-container__commission-container-content">
<div className="pending-awards-container__commission-container-content__icon-box">
<img src={IconExclamation} alt="Ícone exclamação" />
</div>
{pendingAwards.commission
? formatCurrency(pendingAwards.commission)
: 'R$ 0,00'}
</span>
</div>
</div>
</div>
)}
</div>
)
}
export default PendingAwards
My service that makes the API calls it is written like this:
import { useCallback } from 'react'
import axios from 'axios'
const usePanelClient = () => {
const getQuotationCard = useCallback(() => axios.get('/api/cards/quotation'), [])
const getCommissionCard = useCallback(() => axios.get('/api/cards/commission'), [])
const getPendingAwards = useCallback(() => axios.get('/api/premium/pending'), [])
return useCallback(() => ({
getQuotationCard,
getCommissionCard,
getPendingAwards,
}), [
getQuotationCard,
getCommissionCard,
getPendingAwards,
])
}
export default usePanelClient
In my current test I've tried mocking the hook like this, but I did not have success:
import React from 'react'
import { render } from '#testing-library/react'
import { Router } from 'react-router-dom'
import { createMemoryHistory } from 'history'
import PendingAwards from './PendingAwards'
describe('PendingAwards Component', () => {
beforeEach(() => {
jest.mock('../../../clients/PanelClient/usePanelClient', () => {
const mockData = {
quantity: 820,
amount: 26681086.12,
commission: 5528957.841628,
}
return {
getPendingAwards: jest.fn(() => Promise.resolve(mockData)),
}
})
})
it('should render the PendingAwards', () => {
const history = createMemoryHistory()
history.push = jest.fn()
const { container } = render(
<Router history={history}>
<PendingAwards />
</Router>,
)
expect(container).toBeInTheDocument()
})
it('should render the PendingAwards', () => {
const history = createMemoryHistory()
history.push({
search: '&scrollTo=pendingAwards',
})
window.scrollTo = jest.fn()
render(
<Router history={history}>
<PendingAwards />
</Router>,
)
expect(window.scrollTo).toHaveBeenCalledWith({ behavior: 'smooth', top: 0 })
})
})
May someone help me resolving this? I don't feel like this is something hard, but I've tried several things, and nothing seems to resolve it.
Thanks in advance.
You must call jest.mock at the top level of the module and for mocking ES6 modules with a default export you should use __esModule: true
jest.mock('../../../clients/PanelClient/usePanelClient', () => {
const mockData = {
quantity: 820,
amount: 26681086.12,
commission: 5528957.841628,
}
return {
__esModule: true,
default: ()=> ({
getPendingAwards: jest.fn(() => Promise.resolve({data: mockData})),
}),
}});
I'm currently struggling with React Context. I'd like to pass functions allowing the show / hide cart logic in the context, instead of using props between components.
I dont understand why when clicking on the button in HeaderCartButton component, it doesn't trigger the **onClick={ctx.onShowCart}** that is in my context, even though when I console log the cartCtx.state it is properly updated, which should then add the component in the App.js
//App.js
import { useContext } from "react";
import Header from "./components/Layout/Header";
import Meals from "./components/Meals/Meals";
import Cart from "./components/Cart/Cart";
import CartProvider from "./store/CartProvider";
import CartContext from "./store/cart-context";
function App() {
const ctx = useContext(CartContext);
return (
<CartProvider>
{ctx.state && <Cart />}
<Header />
<main>
<Meals />
</main>
</CartProvider>
);
}
export default App;
//cart-context.js
import React from "react";
const CartContext = React.createContext({
state: false,
onShowCart: () => {},
onHideCart: () => {},
items: [],
totalAmount: 0,
addItem: (item) => {},
removeItem: (id) => {},
});
export default CartContext;
//CartProvider.js
import CartContext from "./cart-context";
import { useState } from "react";
const CartProvider = (props) => {
const [cartIsShown, setCartIsShown] = useState(false);
const showCartHandler = () => {
setCartIsShown(true);
};
const hideCartHandler = () => {
setCartIsShown(false);
};
const handleAddItem = (item) => {};
const handleRemoveItem = (id) => {};
const cartCtx = {
state: cartIsShown,
onShowCart: showCartHandler,
onHideCart: hideCartHandler,
items: [],
totalAmount: 0,
addItem: handleAddItem,
removeItem: handleRemoveItem,
};
return (
<CartContext.Provider value={cartCtx}>
{props.children}
</CartContext.Provider>
);
};
export default CartProvider;
//Header.js
import { Fragment } from "react";
import HeaderCartButton from "./HeaderCartButton";
import mealsImage from "../../assets/meals.jpg";
import classes from "./Header.module.css";
const Header = (props) => {
return (
<Fragment>
<header className={classes.header}>
<h1>ReactMeals</h1>
<HeaderCartButton />
</header>
<div className={classes["main-image"]}>
<img src={mealsImage} alt="A table full of delicious food!" />
</div>
</Fragment>
);
};
export default Header;
//HeaderCartButton.js
import CartIcon from "../Cart/CartIcon";
import { useContext } from "react";
import classes from "./HeaderCartButton.module.css";
import CartContext from "../../store/cart-context";
const HeaderCartButton = (props) => {
const ctx = useContext(CartContext);
const numberOfCartItems = ctx.items.reduce((accumulator, item) => {
return accumulator + item.amount;
}, 0);
return (
<button className={classes.button} onClick={ctx.onShowCart}>
<span className={classes.icon}>
<CartIcon />
</span>
<span>Your Cart</span>
<span className={classes.badge}>{numberOfCartItems}</span>
</button>
);
};
export default HeaderCartButton;
Thanks for your help
If you look at your App component, you are using CartContext outside the provider.
function App() {
const ctx = useContext(CartContext);
return (
<CartProvider>
{ctx.state && <Cart />}
<Header />
<main>
<Meals />
</main>
</CartProvider>
);
}
You should modify it so that it is similar to the following, where you are using the context inside the provider.
const Main = () => {
return <CartProvider><App /></CartProvider>
}
function App() {
const ctx = useContext(CartContext);
return (
<>
{ctx.state && <Cart />}
<Header />
<main>
<Meals />
</main>
</>
);
}
I'm currently coding a React -typescript App for practising FluentUI (a.k.a Fabric). Issue appears
with my App.tsx component.
import React, { useContext, useState } from 'react';
import logo from './logo.svg';
import './App.css';
import Search from './components/Search';
//import CategoriasProvider from './components/Context/CategoriasContext';
import Title from './components/Title';
import { ListGhostingExample } from '../src/components/DrinkList';
import { PrimaryButton } from 'office-ui-fabric-react';
import { CategoriasContext, ICategoriasContextInterface } from './components/Context/CategoriasContext';
import CategoriasProvider from './components/Context/CategoriasContext';
import axios from 'axios';
import './components/DrinkList.css'
import './components/Search.css'
interface IApp{
items:ICategoriasContextInterface[],
renderList:boolean
}
const App =()=> {
const contextValues=useContext(CategoriasContext);
return(
<CategoriasProvider>
<div className="App">
<div className="search">
<Search name={contextValues?.name} image={contextValues?.image} thumbnail={contextValues?.thumbnail} />
</div>
</div>
</CategoriasProvider>
);
}
export default App;
CategoriasProvider comes from a Context (CategoriasContext.tsx ). CategoriasProvider has the mentioned error Inside of CategoriasProvider there's a Search.tsx Component.Search's works as a "wrapper". Code is:
import React, { useEffect, useState } from 'react';
import { SearchBox,ISearchBoxStyles } from 'office-ui-fabric-react/lib/SearchBox';
import { PrimaryButton, IContextualMenuProps, Stack, IStackTokens, StackItem, initializeIcons } from 'office-ui-fabric-react';
import { ComboBox, DefaultPalette, Dropdown, DropdownMenuItemType, IComboBoxOption, IDropdownOption, IDropdownStyles, IStackItemStyles, SelectableOptionMenuItemType, Toggle } from '#fluentui/react';
import { getGlassesOriginal } from './Utils/Utils';
import axios from 'axios';
import '../Search.css';
import { CategoriasContext, ICategoriasContextInterface } from './Context/CategoriasContext';
initializeIcons();
const Search = (props:ICategoriasContextInterface) => {
//State
const [textContent, setTextContent] = useState("");
const [textBoxDisabled,disableTextBox]=useState(false);
const [comboBoxDisabled,disableComboBox]=useState(true);
const CategoriasContextInSearch=React.useContext(CategoriasContext);
const setTextContentInstate = (e: any) =>{
console.log("Contenido de e" + e.target.value);
setTextContent(e.target.value);
}
const showMessageInConsole = ():void => {
console.log(textContent);
setTextContent("");
}
// Example formatting
const stackTokens: IStackTokens = { childrenGap: 20 };
const searchBoxStyles: Partial<ISearchBoxStyles> = { root: { width: 200 } };
const dropdownStyles: Partial<IDropdownStyles> = {
dropdown: { width: 200 },
};
const options: IDropdownOption[] = [
{ key: 'glasses', text: 'Glasses', itemType: DropdownMenuItemType.Header },
];
function getGlasses () {
let outputArray:string[] = [];
console.log("getGlasses");
axios
.get("https://www.thecocktaildb.com/api/json/v1/1/list.php?g=list")
.then((response)=>{
let responseDataJson=response.data.drinks;
for (let element in responseDataJson) {
options.push({key:responseDataJson[element].strGlass,text:responseDataJson[element].strGlass});
}
}
)
return outputArray;
}
function selectSearch(){
if(textBoxDisabled){
disableTextBox(false);
disableComboBox(true);
} else {
disableTextBox(true);
disableComboBox(false);
};
}
useEffect(() => {
//TODO: No se debería llamar siempre a esta función. Solamente cuando se activa el sistmea de búsqueda (y además, cachearlo)
getGlasses()
});
return(
<div className="wrapper">
<div className="one"> <Toggle onClick={selectSearch}/></div>
<div className="two">
{
<SearchBox
name="searchBox"
className="searchBox"
styles={searchBoxStyles}
placeholder="Cheers!"
onChange={setTextContentInstate}
value={textContent}
disabled={textBoxDisabled}
/>
}
</div>
<div className="three">
<Dropdown
placeholder="Select a glass"
options={options}
styles={dropdownStyles}
disabled={comboBoxDisabled}
/>
</div>
<div className="four">
<div className="primaryButton">
<PrimaryButton text="Search" onClick={showMessageInConsole}/>
</div>
</div>
</div>
);
}
export default Search;
Hope you can help me!!! Thanks in advance!
The code which is causing the error in your title is in your comment. It's this line:
export const CategoriasProvider = () => {
You are defining CategoriasProvider as a component which takes no props. It can only accept IntrinsicAttributes which is basically just the key property.
But when you use CategoriasProvider in App you are calling it with JSX element children. You get an error because you have not said that the CategoriasProvider component can accept a children prop.
Any of the following types will solve your problem:
export const CategoriasProvider: React.FC = ({children}) => {
export const CategoriasProvider = ({children}: {children: React.ReactNode}) => {
export const CategoriasProvider = ({children}: React.PropsWithChildren<{}>) => {
Regardless, you'll want to pass the children down as children of the inner Provider component.
return (
<CategoriasContext.Provider value={hola}>
{children}
</CategoriasContext.Provider>
);
Your App component is not going to work as expected because the useContext hook which accesses the CategoriasContext is located outside of the CategoriasProvider. It will just get the default value for the context -- not the value from the provider.
You need to rearrange your components such that the hook call occurs in a component that is rendered inside of the CategoriasProvider.
Try this:
const Search = () => {
const contextValues = useContext(CategoriasContext);
return (
<div className="search">
<Search
name={contextValues?.name}
image={contextValues?.image}
thumbnail={contextValues?.thumbnail}
/>
</div>
);
};
const App = () => {
return (
<CategoriasProvider>
<div className="App">
<Search />
</div>
</CategoriasProvider>
);
};
export default App;
I've been struggling with my first solo proyect due to my lack of experience, here is the trouble I have:
I need to set a Link component (of react-router-dom) which takes me to another new page when I clicked the button. Thought that selection I need to pass to the link (and the context) the id so I can fetch the data from the API, but I cannot get an idea of how to make it works. This is the button link component:
Model.jsx
import React, { useState, useContext } from 'react';
import { BrowserRouter as Router, Switch, Route, Link, Redirect } from 'react-router-dom';
import FileModel from "../FileModel/FileModel";
import { ModelsContext } from "../../context/ModelsContext";
const Model = ({modelo}) => {
const { id, name, year, price, photo } = modelo;
const { guardarModelo } = useContext(ModelsContext);
const [display, setDisplay] = useState("btn-notdisplayed");
const showButton = e => {
e.preventDefault();
setDisplay("btn-displayed");
};
const hideButton = e => {
e.preventDefault();
setDisplay("btn-notdisplayed");
};
return (
<div
className="card"
onMouseEnter={e => showButton(e)}
onMouseLeave={e => hideButton(e)}
>
<div className="card-body">
<p className="card-name">{name}</p>
<p className="card-yearprice">{year} | $ {price}</p>
</div>
<img src={`https://challenge.agenciaego.tech${photo}`} className="card-image" alt={`Imagen de ${name}`} />
<Router>
<button
type="button"
className={display}
onClick={() => {
guardarModelo(modelo);
}}
><Link to={`/models/${modelo.id}`}>Ver Modelo</Link>
</button>
<Switch>
<Route exact path={`/models/${modelo.id}`} component={FileModel} />
</Switch>
</Router>
</div>
);
}
export default Model;
Then I obtained the data from a context:
ModelsContext.jsx
import React, { createContext, useState, useEffect } from 'react';
export const ModelsContext = createContext();
const ModelsProvider = (props) => {
//State de modelos
const [ modelo, guardarModelo ] = useState({});
const [ modelos, guardarModelos ] = useState([]);
const [ allModelos, guardarAllModelo ] = useState([]);
//Cargar un modelo
useEffect(() => {
const consultarAPI = async () => {
const api = await fetch("https://challenge.agenciaego.tech/models");
const modelos = await api.json();
const api2 = await fetch(`https://challenge.agenciaego.tech/models/${id}`);
const modelo = await api2.json();
guardarAllModelo(modelos);
guardarModelos(modelos);
guardarModelo(modelo);
}
consultarAPI()
}, []);
return (
<ModelsContext.Provider
value={{
allModelos,
modelo,
modelos,
guardarModelo,
guardarModelos
}}
>
{props.children}
</ModelsContext.Provider>
)
}
export default ModelsProvider;
Finally, I got the App.js from which I route the principal component, the idea Is to get with the Link to the new component called "FileModel.jsx" as a child component an so maintains the Navbar component.
App.js
import React from "react";
import Navbar from "./components/Nav/Navbar";
import Models from "./components/Models/Models";
import { BrowserRouter as Router, Switch, Route, Link } from 'react-router-dom';
import Logo from "./assets/img/logo.png";
import ModelsProvider from "./context/ModelsContext";
import ModelFooter from "./components/Models/ModelFooter";
function App() {
return (
<ModelsProvider>
<Router>
<nav className="navbar">
<img src={Logo} className="logo" alt="Ego Logo" />
<div className="menu-container">
<Link to={'/models'} className="menu-items">Modelos</Link>
<a className="menu-items">Ficha de modelo</a>
</div>
<div className="bottom-line"></div>
</nav>
<Switch>
<Route exact path='/models' component={Models} />
</Switch>
</Router>
<Navbar />
<ModelFooter />
</ModelsProvider>
);
}
export default App;
This is the fileModel.jsx so far:
import React, { useContext } from 'react';
import Navbar from "../Nav/Navbar";
import { ModelsContext } from "../../context/ModelsContext";
const FileModel = () => {
const { modelo } = useContext(ModelsContext);
console.log(modelo.id);
return (
<Navbar />
);
}
export default FileModel;
I hope to have explained my issue clear, and thanks so much to all you caring people!
Cheers!
Ps: Maybe you can find some things to refactor (I will need to check my code later), If you find something like that any help will be appreciated!
UPDATE
Due to Linda recommendation I merged the two contexts into one, and changed some lines of the code I wrote before, I cannot set the state to pass the solo model to the fileModel component and the Link still not working, I was thinking in a functions that can do it, I made another state, a single modelo, but when I click the button I got an error and undefined, because Idk how to set in the state the Id and so pass it to the API call, the terminal says that id in const api2 = await fetch(https://challenge.agenciaego.tech/models/${id}); is not defined.
I FINALLY SOLVED MY PROBLEM!!! I had to rellocate some elements between my component to make the api context take the id and pass it to the FileModel component, and change the Link router to the App.js, this is how I get to the solution:
App.js
import React from "react";
import Navbar from "./components/Nav/Navbar";
import Models from "./components/Models/Models";
import { BrowserRouter as Router, Switch, Route, Link } from 'react-router-dom';
import Logo from "./assets/img/logo.png";
import ModelsProvider from "./context/ModelsContext";
import ModelFooter from "./components/Models/ModelFooter";
import FileModel from "./components/FileModel/FileModel";
function App() {
return (
<ModelsProvider>
<Router>
<nav className="navbar">
<img src={Logo} className="logo" alt="Ego Logo" />
<div className="menu-container">
<Link to={'/models'} className="menu-items">Modelos</Link>
<Link to={`/models/1`} className="menu-items">Ficha del Modelo</Link>
</div>
<div className="bottom-line"></div>
</nav>
<Switch>
<Route exact path='/models' component={Models} />
<Route exact path='/models' component={Models} />
</Switch>
</Router>
<Navbar />
<FileModel />
<ModelFooter />
</ModelsProvider>
);
}
export default App;
Modelscontext.jsx
import React, { createContext, useState, useEffect } from 'react';
export const ModelsContext = createContext();
const ModelsProvider = (props) => {
//State de modelos
const [ modelo, guardarModelo ] = useState([]);
const [ modelos, guardarModelos ] = useState([]);
const [ allModelos, guardarAllModelo ] = useState([]);
const { id } = modelo;
//Cargar un modelo
useEffect(() => {
const consultarAPI = async () => {
const api = await fetch("https://challenge.agenciaego.tech/models");
const modelos = await api.json();
const api2 = await fetch(`https://challenge.agenciaego.tech/models/${id}`);
const modelo = await api2.json();
guardarAllModelo(modelos);
guardarModelos(modelos);
guardarModelo(modelo);
}
consultarAPI()
}, [modelo.id]);
return (
<ModelsContext.Provider
value={{
allModelos,
modelo,
modelos,
guardarModelo,
guardarModelos
}}
>
{props.children}
</ModelsContext.Provider>
)
}
export default ModelsProvider;
Model.js
import React, { useState, useContext } from 'react';
import { Link } from 'react-router-dom';
import { ModelsContext } from "../../context/ModelsContext";
const Model = ({modelo}) => {
const { name, year, price, photo } = modelo;
const { guardarModelo } = useContext(ModelsContext);
const [display, setDisplay] = useState("btn-notdisplayed");
const showButton = e => {
e.preventDefault();
setDisplay("btn-displayed");
};
const hideButton = e => {
e.preventDefault();
setDisplay("btn-notdisplayed");
};
return (
<div
className="card"
onMouseEnter={e => showButton(e)}
onMouseLeave={e => hideButton(e)}
>
<div className="card-body">
<p className="card-name">{name}</p>
<p className="card-yearprice">{year} | $ {price}</p>
</div>
<img src={`https://challenge.agenciaego.tech${photo}`} className="card-image" alt={`Imagen de ${name}`} />
<Link to={`/models/${modelo.id}`}><button
type="button"
className={display}
onClick={() => {
guardarModelo(modelo);
}}
>Ver Modelo
</button></Link>
</div>
);
}
export default Model;
FileModel.js (so far...)
import React, { useContext } from 'react';
import Navbar from "../Nav/Navbar";
import { ModelsContext } from "../../context/ModelsContext";
const FileModel = () => {
const { modelo } = useContext(ModelsContext);
const { photo, name } = modelo;
return (
<>
<Navbar />
<section>
<img src={`https://challenge.agenciaego.tech${photo}`} alt={`Imagen de ${name}`}/>
</section>
</>
);
}
export default FileModel;
Thanks to Linda for giving me the input to refactoring the context! cheers!