I've read that using multiple instances of Pusher is not recommended and therefore I am using React's context feature.
import React, { useContext, useEffect, useRef } from "react";
import Pusher from "pusher-js";
const PusherContext = React.createContext<Pusher | undefined>(undefined);
export const usePusher = () => useContext(PusherContext)
export const PusherProvider: React.FC = (props) => {
const clientRef = useRef<Pusher>();
useEffect(() => {
if (!clientRef.current) {
clientRef.current = new Pusher('pusher_key', {
cluster: 'pusher_cluster',
authEndpoint: 'auth_endpoint',
})
}
}, [clientRef]);
return (
<PusherContext.Provider value={clientRef?.current}>
{props.children}
</PusherContext.Provider>
)
}
Our pusher instance is of type Pusher or undefined. When we call usePusher it is always undefined.
import { usePusher } from "./services/pusher";
const Chat: React.FC = (props) => {
const pusher = usePusher()
useEffect(() => {
if (pusher) {
console.log("pusher is initialized/defined")
} else {
// it is always undefined
console.log("pusher is not initialized/defined")
}
}, [pusher])
return (
<div>
Test component
</div>
)
}
Where is the problem?
It seems to work. I was just doing something wrong when wrapping the provider. Hope it helps in case someone have similar problem!
Related
I am new to react (that I use with typeScript) and I am facing an issue with the use of the useMemo hook.
Here is my fetching service:
export default class FetchingService {
datas: Data[] = [];
constructor() {
this.fetch();
}
async fetch(): Promise<Data[]> {
const d = // await an async array from an api, using Array.flat()
this.datas = d;
console.log(this.datas);
return d;
}
}
In a component, I try to watch for change of the datas attribute of my service:
import fetchingService from '../services/fetchingService.ts';
const Home: React.FC = () => {
const ds: Data[];
const [datas, setDatas] = useState(ds);
const fetchDatas = useMemo(() => {
console.log('Render datas', fetchingService.datas?.length)
setDatas(fetchingService.datas);
return fetchingService.datas;
}, [fetchingService.datas]);
return (
<ul>{datas.map(d => {
return (
<li key={d.id}>{d.id}</li>
);
</ul>
);
}
The problem I am facing is that the useMemo hook is not recompouted when the datas attribute changes within my fetchService. I am pretty sure that my FetchingService.fetch() function works because the console.log within the fetch function always display the fetched datas.
The observed behavior is that sometimes datas are well rendered (when fetch ends before rendering ...), but sometimes it isn't.
The expected one is that datas are rendered every time and only on refresh, exept when datas are modified
I also tried to put the length of the data array as a dependency in useMemo, but in both cases it doesn't work and I have a warning in my IDE, telling me it is an unnecessary dependency.
I don't really understand if it is a typescript or a specific react behavior issue. I think the reference of the datas attribute should change at the end of the fetch (or at least its length attribute ...), but tell me if I am wrong.
I do appreciate every help !
in fetchingService, when datas change, probably the dependency cannot be accepted. You can use a custom hook in stead of it.
You can use this source about useMemo: useMemo with an array dependency?
import { useState, useLayoutEffect, useCallback } from "react";
export const useFetchingService = () => {
const [fetchedData, setFetchedData] = useState([]);
const fetch = useCallback(async () => {
const d = await new Promise((res, rej) => {
setTimeout(() => {
res([1, 2, 3]);
}, 5000);
}); // await an async array from an api, using Array.flat()
setFetchedData(d);
}, []);
useLayoutEffect(() => {
fetch();
}, []);
return [fetchedData];
};
useLayoutEffect runs before rendering
using:
const [fetchData] = useFetchingService();
const fetchDatas = useMemo(async () => {
console.log("Render datas", fetchData.length);
setDatas(fetchData);
return fetchData;
}, [fetchData]);
You can also use this directly without 'datas' state.
I hope that this will be solution for you.
So I put together a codesandbox project that uses a context to store the value:
App.tsx
import React, { useState, useEffect, createContext } from "react";
import Home from "./Home";
export const DataContext = createContext({});
export default function App(props) {
const [data, setData] = useState([]);
useEffect(() => {
const get = async () => {
const d = await fetch("https://dummyjson.com/products");
const json = await d.json();
const products = json.products;
console.log(data.slice(0, 3));
setData(products);
return products;
};
get();
}, []);
return (
<div>
Some stuff here
<DataContext.Provider value={{ data, setData }}>
<Home />
</DataContext.Provider>
</div>
);
}
Home.tsx
import React, { FC, useMemo, useState, useEffect, useContext } from "react";
import { DataContext } from "./App";
import { Data, ContextDataType } from "./types";
const Home: FC = () => {
const { data, setData }: ContextDataType = useContext(DataContext);
return (
<>
<ul>
{data.map((d) => {
return (
<li key={d.id}>
{d.title}
<img
src={d.images[0]}
width="100"
height="100"
alt={d.description}
/>
</li>
);
})}
</ul>
</>
);
};
export default Home;
This was my first time using both codesandbox and typescript so I apologize for any mistakes
I have an issue with react won't update ui, I am trying to update the number of people connected in the same room there's no issue in the backend, my issue is on the front because I saw that the events are reaching the client through chrome dev tools.
as shown below the event is indeed reaching the client.
import React, { useContext, useEffect, useState, useCallback } from 'react';
import { useParams } from 'react-router-dom';
import { SocketContext } from '../context/socket';
type Props = {};
export default function Game({}: Props) {
const socket = useContext(SocketContext);
const { id } = useParams();
const [playerCount, setPlayerCount] = useState(0);
const updatePlayerCount = (...args: string[]) => {
console.log(args);
setPlayerCount(args.length);
};
useEffect(() => {
socket.emit('join_room', id);
socket.on('game_result', gameHandler);
socket.on('player_count', updatePlayerCount);
return () => {
socket.off('game_result');
socket.off('player_count');
};
}, []);
const gameHandler = (...args: any) => {
console.log(args);
};
return (
<div>
Game {id}
<div>{playerCount}</div>
</div>
);
}
checking the console I do see my console.log firing...
however the first join event does work cause I don't see 0 I see 1 instead. playerCount = 0 initially
You can try defining both your event handlers inside your useEffect() like this
useEffect(() => {
const updatePlayerCount = (...args: string[]) => {
console.log(args);
setPlayerCount(args.length);
};
const gameHandler = (...args: any) => {
console.log(args);
};
socket.emit('join_room', id);
socket.on('game_result', gameHandler);
socket.on('player_count', updatePlayerCount);
return () => {
socket.off('game_result');
socket.off('player_count');
};
}, [id]);
apparently I didn't pay attention that the data emitted was an array embedded inside an array
a simple fix was
setPlayerCount(args[0].length);
I have this hook:
import { useEffect, useState } from 'react';
import { auth } from "../firebase/auth-service"
const useFirebaseAuthentication = () => {
const [authUser, setAuthUser] = useState(null);
useEffect(() => {
const unlisten = auth.onAuthStateChanged(
authUser => {
authUser
? setAuthUser(authUser)
: setAuthUser(null);
},
);
return () => {
unlisten();
}
});
return authUser
}
export default useFirebaseAuthentication;
That I use like this:
const authUser = useFirebaseAuthentication();
Is there a way I could use this hook useFirebaseAuthentication() in a class-based component?
Class based components directly donot support Hooks.
Read React-FAQ
You can create a Higher Order Component, like this:
import React from 'react';
import { useFirebaseAuthentication} from '../hooks/useFirebaseAuthentication';
export const withFireBaseAuthHookHOC = (Component: any) => {
return (props: any) => {
const authUser = useFirebaseAuthentication();
return <Component authUser ={authUser} {...props} />;
};
};
Now just wrap your class based component with this HOC.
.
I'm writing a test code with Jest for a custom hook in my web application.
It uses Recoil for state management, but the error message appears when I run npm run test.
This is the error message.
This component must be used inside a <RecoilRoot> component.
16 | const useIds = () => {
17 | // const [ids, setIds] = React.useState([]);
> 18 | const [ids, setIds] = useRecoilState(idsState);
| ^
This is the test code.
import * as React from 'react';
import { render, fireEvent } from '#testing-library/react';
import { useIds } from '#/hooks/useIds';
import { RecoilRoot } from 'recoil';
it('unit test for custom hook useIds', () => {
const TestComponent: React.FC = () => {
const ids = useIds();
return (
<RecoilRoot>
<div title='ids'>{ ids }</div>
</RecoilRoot>
)
}
const { getByTitle } = render(<TestComponent />);
const ids = getByTitle('ids');
})
This is the custom hook code
import * as React from 'react';
import { useRouter } from 'next/router';
import { atom, useRecoilState } from 'recoil';
import { fetchIdsByType } from '#/repositories';
const initialState: {
[type: string]: number[];
} = {};
export const idsState = atom({
key: 'idsState',
default: initialState,
});
const useIds = () => {
const [ids, setIds] = useRecoilState(idsState);
const router = useRouter();
const { type } = router.query;
React.useEffect(() => {
if (router.asPath !== router.route) {
// #ts-ignore
fetchIdsByType(type).then((ids: number[]) => {
setIds((prevState) => {
return {
...prevState,
// #ts-ignore
[type]: ids,
};
});
});
}
}, [router]);
// #ts-ignore
return ids[type];
};
export { useIds };
I know why the error is happening but I have no idea where the RecoilRoot should be in?
You might need to put where to wrap the component which is using your custom hook as following:
it('unit test for custom hook useIds', () => {
const TestComponent: React.FC = () => {
const ids = useIds();
return (
<div title='ids'>{ ids }</div>
)
}
const { getByTitle } = render(
// Put it here to wrap your custom hook
<RecoilRoot>
<TestComponent />
</RecoilRoot>
);
const ids = getByTitle('ids');
})
I have a React Native App,
Here i use mobx ("mobx-react": "^6.1.8") and react hooks.
i get the error:
Invalid hook call. Hooks can only be called inside of the body of a function component
Stores index.js
import { useContext } from "react";
import UserStore from "./UserStore";
import SettingsStore from "./SettingsStore";
const useStore = () => {
return {
UserStore: useContext(UserStore),
SettingsStore: useContext(SettingsStore),
};
};
export default useStore;
helper.js OLD
import React from "react";
import useStores from "../stores";
export const useLoadAsyncProfileDependencies = userID => {
const { ExamsStore, UserStore, CTAStore, AnswersStore } = useStores();
const [user, setUser] = useState({});
const [ctas, setCtas] = useState([]);
const [answers, setAnswers] = useState([]);
useEffect(() => {
if (userID) {
(async () => {
const user = await UserStore.initUser();
UserStore.user = user;
setUser(user);
})();
(async () => {
const ctas = await CTAStore.getAllCTAS(userID);
CTAStore.ctas = ctas;
setCtas(ctas);
})();
(async () => {
const answers = await AnswersStore.getAllAnswers(userID);
UserStore.user.answers = answers.items;
AnswersStore.answers = answers.items;
ExamsStore.initExams(answers.items);
setAnswers(answers.items);
})();
}
}, [userID]);
};
Screen
import React, { useEffect, useState, useRef } from "react";
import {
View,
Dimensions,
SafeAreaView,
ScrollView,
StyleSheet
} from "react-native";
import {
widthPercentageToDP as wp,
heightPercentageToDP as hp
} from "react-native-responsive-screen";
import { observer } from "mobx-react";
import useStores from "../../stores";
import { useLoadAsyncProfileDependencies } from "../../helper/app";
const windowWidth = Dimensions.get("window").width;
export default observer(({ navigation }) => {
const {
UserStore,
ExamsStore,
CTAStore,
InternetConnectionStore
} = useStores();
const scrollViewRef = useRef();
const [currentSlide, setCurrentSlide] = useState(0);
useEffect(() => {
if (InternetConnectionStore.isOffline) {
return;
}
Tracking.trackEvent("opensScreen", { name: "Challenges" });
useLoadAsyncProfileDependencies(UserStore.userID);
}, []);
React.useEffect(() => {
const unsubscribe = navigation.addListener("focus", () => {
CTAStore.popBadget(BadgetNames.ChallengesTab);
});
return unsubscribe;
}, [navigation]);
async function refresh() {
const user = await UserStore.initUser(); //wird das gebarucht?
useLoadAsyncProfileDependencies(UserStore.userID);
if (user) {
InternetConnectionStore.isOffline = false;
}
}
const name = UserStore.name;
return (
<SafeAreaView style={styles.container} forceInset={{ top: "always" }}>
</SafeAreaView>
);
});
so now, when i call the useLoadAsyncProfileDependencies function, i get this error.
The Problem is that i call useStores in helper.js
so when i pass the Stores from the Screen to the helper it is working.
export const loadAsyncProfileDependencies = async ({
ExamsStore,
UserStore,
CTAStore,
AnswersStore
}) => {
const userID = UserStore.userID;
if (userID) {
UserStore.initUser().then(user => {
UserStore.user = user;
});
CTAStore.getAllCTAS(userID).then(ctas => {
console.log("test", ctas);
CTAStore.ctas = ctas;
});
AnswersStore.getAllAnswers(userID).then(answers => {
AnswersStore.answers = answers.items;
ExamsStore.initExams(answers.items);
});
}
};
Is there a better way? instead passing the Stores.
So that i can use this function in functions?
As the error says, you can only use hooks inside the root of a functional component, and your useLoadAsyncProfileDependencies is technically a custom hook so you cant use it inside a class component.
https://reactjs.org/warnings/invalid-hook-call-warning.html
EDIT: Well after showing the code for app.js, as mentioned, hook calls can only be done top level from a function component or the root of a custom hook. You need to rewire your code to use custom hooks.
SEE THIS: https://reactjs.org/docs/hooks-rules.html
You should return the value for _handleAppStateChange so your useEffect's the value as a depdendency in your root component would work properly as intended which is should run only if value has changed. You also need to rewrite that as a custom hook so you can call hooks inside.
doTasksEveryTimeWhenAppWillOpenFromBackgorund and doTasksEveryTimeWhenAppGoesToBackgorund should also be written as a custom hook so you can call useLoadAsyncProfileDependencies inside.
write those hooks in a functional way so you are isolating specific tasks and chain hooks as you wish without violiating the rules of hooks. Something like this:
const useGetMyData = (params) => {
const [data, setData] = useState()
useEffect(() => {
(async () => {
const apiData = await myApiCall(params)
setData(apiData)
})()
}, [params])
return data
}
Then you can call that custom hook as you wish without violation like:
const useShouldGetData = (should, params) => {
if (should) {
return useGetMyData()
}
return null
}
const myApp = () => {
const myData = useShouldGetData(true, {id: 1})
return (
<div>
{JSON.stringify(myData)}
</div>
)
}