I am trying to call a function from a different component but when I console.log('hi') it appear but it didn't call the messageContext.
Here is my follwing code from Invitees.js:
const [showPreview, setShowPreview] = useState(false);
const toggleUserPreview = () => {
setShowPreview(!showPreview);
};
{showPreview && (
<ResultsWrappers togglePreview={toggleUserPreview}>
<UserPreview
userInfo={applicant}
skillStr={applicant.Skills}
togglePreview={toggleUserPreview}
/>
</ResultsWrappers>
)}
Here is the component have the function I want to call UserPreview.js:
import { useMessageContextProvider } from "../context/MessageContext";
const UserPreview = ({ userInfo, skillStr, togglePreview }) => {
const messageContextProvider = useMessageContextProvider();
const messageUser = () => {
togglePreview();
messageContextProvider.updateActiveUserToMessage(userInfo);
console.log('hi');
};
...
};
Here is my messageContext:
import { createContext, useContext, useState } from "react";
const messageContext = createContext();
export const MessageContextProvider = ({ children }) => {
const [activeUserToMessage, setActiveUserToMessage] = useState({});
const [isOpenMobileChat, toggleMobileChat] = useState(false);
const updateActiveUserToMessage = (user) => {
setActiveUserToMessage(user);
};
return (
<messageContext.Provider
value={{
updateActiveUserToMessage,
activeUserToMessage,
isOpenMobileChat,
toggleMobileChat,
}}
>
{children}
</messageContext.Provider>
);
};
export const useMessageContextProvider = () => {
return useContext(messageContext);
};
When the messageContext called it should open the chatbox like this:
The code you showing is not enough to say it for 100%, but it seems like toggleUserPreview - function called twice, so it reverted to original boolean value.
One time as <ResultsWrappers togglePreview={toggleUserPreview}/>
and second time as <UserPreview togglePreview={toggleUserPreview}/>.
Related
So I am trying to store a global state using context to allow me to use the same state across different components.
The issue I am having is that when I set the global state in 1 component and try to access it in the other component to use the state. It appears to be null and I cannot figure out why?
The first component where I set the global state in will always be rendered before the component shown that seems to have an empty value for the global state.
GlobalStateProvider component:
import React from "react";
import { useState, useEffect } from "react";
import axios from "axios";
const defaultActivitiesState = [];
const globalStateContext = React.createContext(defaultActivitiesState);
const dispatchStateContext = React.createContext([]);
export const useGlobalState = () =>
[
React.useContext(globalStateContext),
React.useContext(dispatchStateContext)
];
const GlobalStateProvider = ({ children }) => {
const [state, dispatch] = React.useReducer((state, newValue) => (state, newValue),
defaultActivitiesState
);
return (
<globalStateContext.Provider value={state}>
<dispatchStateContext.Provider value={dispatch}>
{children}
</dispatchStateContext.Provider>
</globalStateContext.Provider>
);
}
export default GlobalStateProvider;
Component I set the global state in:
import react from "react";
import { useState, useEffect, useMemo } from "react";
import { MapContainer, TileLayer, Popup, Polyline } from "react-leaflet";
import axios from "axios";
import polyline from "#mapbox/polyline";
import MapComp from "./MapComp";
import { useGlobalState } from "./GlobalStateProvider";
function Map() {
// ------- global state
const [activities, setActivities] = useGlobalState(); // global state
//const [activities, setActivities] = useState([]);
//const [polylines, setPolylines] = useState(null); // as empty array value is still truthy
const [isLoading, setIsLoading] = useState(true);
const [mapMode, setMapMode] = useState("light");
const [mapStyle, setMapStyle] = useState(
"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
);
const [showMarkers, setShowMarkers] = useState(true);
useEffect(() => {
setActivitieData();
console.log("activities after useEffect", activities)
}, []);
const polylines = useMemo(() => {
console.log("activities inside memo", activities)
console.log("activities.len =", activities.length);
if (activities.length) {
console.log("past len");
const polylineArray = [];
for (const item of activities) {
const polylineData = item.map.summary_polyline;
const activityName = item.name;
const activityType = item.type;
polylineArray.push({
positions: polyline.decode(polylineData),
name: activityName,
activityType: activityType,
});
}
setIsLoading(false);
return polylineArray;
}
return null;
}, [activities]);
const toggleMarkers = () => {
setShowMarkers((show) => !show);
};
const getActivityData = async () => {
console.log("calling")
const response = await axios.get(
"http://localhost:8800/api/"
);
return response.data;
};
const setActivitieData = async () => {
const activityData = await getActivityData();
setActivities(activityData);
console.log("Global activities state = ", activities);
};
return !isLoading && polylines ? (
<>
<div className="select-container">
<button className="toggle-markers" onClick={() => toggleMarkers()}>
Toggle Markers
</button>
</div>
<MapComp
className={`${mapMode}`}
activityData={{ polylines }}
showMarkers={showMarkers}
/>
</>
) : (
<div>
<p>Loading...</p>
</div>
);
}
export default Map;
component that has an empty value for global state:
import React from 'react';
import { useGlobalState } from './GlobalStateProvider';
function ActivityList() {
const [activities, setActivities] = useGlobalState();
let displayValues;
displayValues =
activities.map((activity) => {
return (
<div>
<p>{activity.name}</p>
<p>{activity.distance}m</p>
</div>
);
})
return (
<>
<p>Values</p>
{displayValues}
</>
);
}
export default ActivityList;
App.js:
function App() {
return (
<GlobalStateProvider>
<div className="App">
<NavBar />
<AllRoutes />
</div>
</GlobalStateProvider>
);
}
export default App;
Say I have a hook function that provides a useEffect
export function useTokenCheckClock(authState: AuthState, timeout: number = 60) {
const [lastCheckTime, updateLastCheckTime] =
useReducer(updatePerSecondReducer, Math.ceil(Date.now()/1000) * 1000);
useEffect(()=>{
... do something ...
return ()=> { ... do cleanup ... }
}, [authState, timeout] // <-- trying to determine if I need this
}
And a component uses this function as:
function MyComponent() {
const [authState, setAuthState] = useState(...);
useTokenCheckClock(authState);
... some handler logic or useEffect that may alter authState ...
return <>...</>
}
When the authState changes and triggers a render. Would the following occur?
the useEffect hook cleanup function in useTokenCheckClock is called
then the useEffect hook in useTokenCheckClock is called again
It needs it otherwise the effects won't trigger when the parameters change.
This is demonstrated by the following Jest file
import { cleanup, fireEvent, render } from '#testing-library/react-native';
import React, { useEffect, useState } from 'react';
import { Pressable, Text } from 'react-native';
afterEach(cleanup);
beforeEach(() => {
jest.useFakeTimers({ advanceTimers: true });
});
it("does not work with empty dep list", () => {
const useEffectCallback = jest.fn();
const useEffectCleanup = jest.fn();
const rendered = jest.fn();
function useMyHook(state: string, nonState: number) : number{
const [ myState, setMyState ] = useState(Date.now());
useEffect(()=>{
useEffectCallback();
setMyState(Date.now());
return () => useEffectCleanup();
}, [])
return myState;
}
function MyComponent() {
const [authState, setAuthState] = useState("AuthState.INITIAL");
const lastCheckTime = useMyHook(authState, 10);
rendered({authState, lastCheckTime});
return <Pressable onPress={() => { setAuthState("AuthState.AUTHENTICATED") }} ><Text testID="lastCheckTime">{lastCheckTime}</Text></Pressable>
}
jest.setSystemTime(new Date("2025-01-01T20:00:00Z"));
const {getByTestId, unmount} = render(<MyComponent />);
expect(getByTestId("lastCheckTime")).toHaveTextContent("1735761600000")
expect(useEffectCallback).toBeCalledTimes(1)
expect(useEffectCleanup).toBeCalledTimes(0)
expect(rendered).toBeCalledTimes(1)
jest.advanceTimersByTime(1000);
fireEvent.press(getByTestId("lastCheckTime"))
// remains the same
expect(getByTestId("lastCheckTime")).toHaveTextContent("1735761600000")
expect(useEffectCallback).toBeCalledTimes(1)
expect(useEffectCleanup).toBeCalledTimes(0)
// rendered still because of auth state change
expect(rendered).toBeCalledTimes(2)
unmount();
expect(useEffectCallback).toBeCalledTimes(1)
expect(useEffectCleanup).toBeCalledTimes(1)
expect(rendered).toBeCalledTimes(2)
})
it("works with dep list", () => {
const useEffectCallback = jest.fn();
const useEffectCleanup = jest.fn();
const rendered = jest.fn();
function useMyHook(state: string, nonState: number) : number{
const [ myState, setMyState ] = useState(Date.now());
useEffect(()=>{
useEffectCallback();
setMyState(Date.now());
return () => useEffectCleanup();
}, [state, nonState])
return myState;
}
function MyComponent() {
const [authState, setAuthState] = useState("AuthState.INITIAL");
const lastCheckTime = useMyHook(authState, 10);
rendered({authState, lastCheckTime});
return <Pressable onPress={() => { setAuthState("AuthState.AUTHENTICATED") }} ><Text testID="lastCheckTime">{lastCheckTime}</Text></Pressable>
}
jest.setSystemTime(new Date("2025-01-01T20:00:00Z"));
const {getByTestId, unmount} = render(<MyComponent />);
expect(getByTestId("lastCheckTime")).toHaveTextContent("1735761600000")
expect(useEffectCallback).toBeCalledTimes(1)
expect(useEffectCleanup).toBeCalledTimes(0)
expect(rendered).toBeCalledTimes(1)
jest.advanceTimersByTime(1000);
fireEvent.press(getByTestId("lastCheckTime"))
expect(getByTestId("lastCheckTime")).toHaveTextContent("1735761601000")
expect(useEffectCallback).toBeCalledTimes(2)
expect(useEffectCleanup).toBeCalledTimes(1)
// authenticated which then calls set state to trigger two renders
expect(rendered).toBeCalledTimes(3)
unmount();
expect(useEffectCallback).toBeCalledTimes(2)
expect(useEffectCleanup).toBeCalledTimes(2)
expect(rendered).toBeCalledTimes(3)
})
I'm developing a pokedex using pokeAPI through react, but I'm developing a feature where I can favorite pokemons and with that through a context, I can store the names of these pokemons in a global array. I've already managed to test and verify that the pokemon names are going to this "database" array inside the pokeDatabase const in my context, but my goal now is to pass this array to localstorage so that the browser recognizes these favorite pokemons instead of disappearing every time I refresh the page, my solution was to try to create a useEffect inside the context so that every time I refresh my application, this information is saved in localStorage, but without success. What better way to achieve this?
context:
import { createContext } from "react";
const CatchContext = createContext({
pokemons: null,
});
export default CatchContext;
provider
import React, { useEffect } from "react";
import CatchContext from "./Context";
const pokeDatabase = {
database: [],
};
const CatchProvider = ({ children }) => {
useEffect(() => {
const dataStorage = async () => {
await localStorage.setItem('pokemons', JSON.stringify(pokeDatabase.database));
}
dataStorage();
}, [])
return (
<CatchContext.Provider value={{ pokemons: pokeDatabase }}>
{children}
</CatchContext.Provider>
);
}
export default CatchProvider;
pageAddPokemon
import * as C from './styles';
import { useContext, useEffect, useState } from 'react';
import { useApi } from '../../hooks/useApi';
import { useNavigate, useParams } from 'react-router-dom';
import PokeInfo from '../../components/PokeInfo';
import AddCircleOutlineIcon from '#mui/icons-material/AddCircleOutline';
import DoNotDisturbOnIcon from '#mui/icons-material/DoNotDisturbOn';
import CatchContext from '../../context/Context';
const SinglePokemon = () => {
const api = useApi();
const { pokemons } = useContext(CatchContext);
const { name } = useParams();
const navigate = useNavigate();
const handleHompage = () => {
navigate('/');
}
const [loading, setLoading] = useState(false);
const [imgDatabase, setImgDatabase] = useState('');
const [infoPokemon, setInfoPokemon] = useState([]);
const [pokemonTypes, setPokemonTypes] = useState([]);
const [isCatch, setIsCatch] = useState(false);
useEffect(() => {
const singlePokemon = async () => {
const pokemon = await api.getPokemon(name);
setLoading(true);
setImgDatabase(pokemon.sprites);
setInfoPokemon(pokemon);
setPokemonTypes(pokemon.types);
setLoading(false);
console.log(pokemons.database);
}
singlePokemon();
verifyPokemonInDatabase();
}, []);
const verifyPokemonInDatabase = () => {
if (pokemons.database[infoPokemon.name]) {
return setIsCatch(true);
} else {
return setIsCatch(false);
}
}
const handleCatchAdd = async () => {
if (isCatch === false) {
if (!pokemons.database[infoPokemon.name]);
pokemons.database.push(infoPokemon.name);
setIsCatch(true);
}
}
const handleCatchRemove = async () => {
if (isCatch === true) {
if (!pokemons.database[infoPokemon.name]);
pokemons.database.splice(pokemons.database.indexOf(toString(infoPokemon.name)), 1);
setIsCatch(false);
}
}
return (
<C.Container>
<PokeInfo
name={infoPokemon.name}
/>
<C.Card>
<C.Info>
<C.Imgs>
<img src={imgDatabase.front_default} alt="" />
<img src={imgDatabase.back_default} alt="" />
</C.Imgs>
<h2 id='types'>Tipos</h2>
{pokemonTypes.map(type => {
return (
<C.Types>
<h2>{type.type.name}</h2>
</C.Types>
)
})}
{isCatch ? (
<DoNotDisturbOnIcon id='iconCatched' onClick={handleCatchRemove}/>
): <AddCircleOutlineIcon id='icon' onClick={handleCatchAdd}/>}
</C.Info>
</C.Card>
<C.Return>
<button onClick={handleHompage}>Retornar a Pokédex</button>
</C.Return>
</C.Container>
)
}
export default SinglePokemon;
I'm so confused about useState in React hooks.
I do not know why console.log in setTimeout function calls more than one time when I use useState.
If I remove useState it normally calls only once.
And If I use Class state instead hooks, it normally calls only once as well.
Why is it happened that ?
And how can I handle it ?
(here is my code)
import React, { useState, useEffect } from "react";
import "./App.css";
const usePassword = () => {
const [passwordValue, setPasswordValue] = useState({
password: "",
passwordHidden: "",
});
let timer = null;
const trigger = () => {
clearTimeout(timer);
timer = setTimeout(() => console.log("end"), 1000);
};
const onPasswordChanged = (name, value) => {
setPasswordValue((prev) => ({ ...passwordValue, passwordHidden: value }));
trigger();
};
return { passwordValue, onPasswordChanged };
};
function App() {
const { passwordValue, onPasswordChanged } = usePassword();
const onChanged = (event) => {
const { name, value } = event.target;
onPasswordChanged(name, value);
};
const onSubmit = () => {
console.log("submitted!", passwordValue);
};
return (
<div className="App">
<header className="App-header">
<input name="password" onKeyUp={onChanged} />
<button onClick={onSubmit}>Submit</button>
</header>
</div>
);
}
export default App;
Whenever you set the state using useState you get a new timer variable, as the function is called again. This is why your clearTimeout does not work.
You can use a ref to hold on to the value between render cycles:
const timer = useRef(null);
const trigger = () => {
clearTimeout(timer.current);
timer.current = setTimeout(() => console.log("end"), 1000);
};
I am checking to see if isFetchingData then don't render yet but its not re-rendering once isFetchingData is set to false. I have the useEffect in the context and i would hope that would re-render once isFetchingData is set to false. Any ideas?
When I refresh the page it renders with the data. So I think its to do with re-rendering.
I am using react context to get the data and exposing functions to filter that data and get me what i need.
Context:
import React, { useEffect, useState } from 'react';
import getAllEmployees from 'my-services/employee/getAllEmployees';
import { arrayOf, node, oneOfType } from 'prop-types';
export const EmployeeContext = React.createContext({
allEmployees: [],
getActiveEmployees: () => [],
getTerminatedEmployees: () => []
});
const EmployeesProvider = ({ children }) => {
const [isFetchingData, setIsFetchingData] = useState(true);
const [allEmployees, setAllEmployees] = useState({});
useEffect(() => {
getAllEmployees().then(
//doing something
).then(employees => {
setAllEmployees(employees);
setIsFetchingData(false);
});
}, [isFetchingData])
const context = {
isFetchingData,
allEmployees,
getActiveEmployees: () =>
allEmployees.filter(x => x.status === 'Current'),
getTerminatedEmployees: () =>
allEmployees.filter(x => x.status === 'Terminated')
};
return (
<EmployeeContext.Provider value={context}>{children}</EmployeeContext.Provider>
);
};
EmployeesProvider.propTypes = {
children: oneOfType([node, arrayOf(node)])
};
EmployeesProvider.defaultProps = {
children: undefined
};
export default EmployeesProvider;
Component:
import React, { useContext } from 'react';
import styled from 'styled-components';
import { EmployeeContext } from 'my-contexts/EmployeeContext';
import EmployeeCard from '../../../components/EmployeeCard';
const EmployeesTab = () => {
const {
getActiveEmployees,
getTerminatedEmployees,
isFetchingData
} = useContext(EmployeeContext);
let activeEmployees = [];
let terminatedEmployees = [];
if (!isFetchingData) {
activeEmployees = getActiveEmployees();
terminatedEmployees = getTerminatedEmployees();
}
if(isFetchingData) {
return <p>Loading</p>;
}
return (
<Outer>
<TopHeader>
<H3>Employees ({activeEmployees.length})</H3>
</TopHeader>
<Wrapper>
{activeEmployees.map(employee => {
return (
<EmployeeCard
id={employee.id}
guid={employee.guid}
firstName={employee.name.first}
lastName={employee.name.last}
jobTitle={employee.jobTitle}
/>
);
})}
</Wrapper>
<H3>Terminated employees({terminatedEmployees.length})</H3>
<Wrapper>
{terminatedEmployees.map(employee => {
return (
<EmployeeCard
id={employee.id}
guid={employee.guid}
firstName={employee.name.first}
lastName={employee.name.last}
jobTitle={employee.jobTitle}
/>
);
})}
</Wrapper>
</Outer>
);
};
export default EmployeesTab;
I think many problems may exist.
At first, please check whether whole component is closed by context Provider.
For example
<EmployeesProvider>
<EmployeesTab/>
<EmployeesProvider/>
Please check this problem.