Understand error destroy is not a function - javascript

I don't understand, I'm trying to improve my app.js code by adding a function 'languageSetUp()' to useEffect and I find myself facing the error:
destroy is not a function. (In 'destroy()', 'destroy' is an instance
of Object)
Do you know what it is due to? And what does that mean? I do not understand where the problem is, if you can help me and especially explain to me, I thank you in advance. Thanks for any time or help offered.
After search I think it's due to 'this.props' I don't know how to replace it in a functionnal component at first I wanted to do :
if (isConnected === true && this.props && this.props.navigation) {
this.props.navigation.navigate("BottomTabNavigator");
} }
export default function App() {
Text.defaultProps = Text.defaultProps || {};
Text.defaultProps.allowFontScaling = false;
const [user, setUser] = useState({ loggedIn: false });
const state = { user, setUser };
const [loading, setLoading] = useState(true);
async function languageSetUp() {
let lang = await retrieveAppLang();
let isConnected = await userSessionActive();
if (lang.length == 2) {
i18n.changeLanguage(lang);
}
if (isConnected === true) {
this.props.navigation.navigate("BottomTabNavigator");
}
}
React.useEffect(() => {
setTimeout(() => setLoading(false), 2000);
languageSetUp();
if (loading) {
return <Splash />;
}
});
return (
<AppContext.Provider value={state}>
<NavigationContainer>
{!user.loggedIn ? (
<MainStackNavigator />
) : (
<BottomTabNavigator />
)}
</NavigationContainer>
</AppContext.Provider>
);
}

It's coming from your useEffect function. There are two problems with it:
If you return anything from useEffect, it needs to be a function. You can read more about the useEffect cleanup function here: Getting error after I put Async function in useEffect
useEffect should have a second argument, which is a dependency array. The official documentation can give you some more information about how to use it: https://reactjs.org/docs/hooks-effect.html In your case, it looks like you want to run it just once when the component mounts, so it may just be an empty array ([]) as the second parameter.
It looks like what you're trying to do is show a Splash in the event that you're in a loading state.
Instead of returning that from useEffect, you should probably return it from your component. One (simplistic) option would be:
if (loading) {
return <Splash/>;
}
return (
<AppContext.Provider value={state}>
//...
However, be aware that you have other logic errors. Right now, your loading state gets turned off after 2 seconds no matter what. You should probably wait for the return of languageSetUp and set it based on that.
Maybe something like:
React.useEffect(() => {
languageSetUp().then(() => setLoading(false))
//handle errors?
},[]);

Related

why this setState is not firing?

I'm trying to build a notification React component for my application that I can call from anywhere. I have the below code where I'm trying to do some kinda hack for exporting the showNotif function
function FloatNotification() {
const [show, setShow] = useState(false);
const showNotif = () => {
console.log(`showNotif is called`);
setShow(true);
};
if (!FloatNotification.showNotification) {
FloatNotification.showNotif = showNotif;
}
return <>{show ? `Showing notification` : `not showing notification`}</>
}
export default FloatNotification;
On another file I'm trying to call showNotif like below
import FloatNotification from "./FloatNotification";
function MyComponent() {
const {showNotif} = FloatNotification;
return <><button onClick={() => showNotif()}>Click Me</button></>
}
but the setState isn't getting called unexpectedly. I'm getting the showNotif is called message in the console. so logically the setState should also get called.
I kinda understand it's happening because of how javascript handles reference data type. but I'm not sure what's actually happening behind the scene and how to get my goal
Suggest me if you have any other ideas to build this notification component (something I can call from anywhere in my component tree). Any kind of help will be kudos to me
[NOTE: I'm actually using NextJS and I've added this FloatNotification in the _app.js. So it's available in all the pages
useState is a special function called "hooks". React hooks are only available when the component is rendered in the VDOM tree.
Since you dosen't render FloatNotification as a element, calling setState is unexpected and may has no effects.
There are several ways to achieve what you want without hacking.
First is, lift up notification state to the parent component and inject only the dispatch that changes the state is through the context.
const NotificationContext = React.createContext(() => {});
function FloatNotification({ children }) {
const [show, setShow] = useState(false);
return (
<NotificationContext.Provider value={setShow}>
{children}
<>{show ? `Showing notification` : `not showing notification`}</>
</NotificationContext.Provider>
);
}
function MyComponent() {
const setShow = useContext(NotificaitonContext);
return (
<button onClick={() => setShow(true)}>
Show Notification
</button>
);
}
function App() {
return (
<FloatNotification>
<MyComponent />
</FloatNotification>
);
}
Or, you can exposing the handler by React.useImperativeHandle (commonly not recommended)

Return component message for 5 seconds and then redirect

I am wanting to implement the case where if I navigate to my URL with incorrect query parameters, a message is displayed and then I redirect to another page.
Imagine trying to navigate to a page only a logged in user can see when you're not logged in. I want it to render something like, 'You need to be logged in to see this content' and then after 2 - 5 seconds the page redirects to the /login page.
Note: Some of the code included is just pseudo-code.
I know that I can display either the logged in page or redirect with a simple Ternary
return hasQueryParams ? <MyLoggedInPage /> : <Redirect to={`/login`} />
however, I can't seem to get a setTimeout working to delay the redirect...
const redirect = () => {
let redirect = false;
setTimeout(() => {
redirect = true;
}, 5000);
return redirect
? <Redirect to={`/login`} />
: <h1>Need to be logged in for that</h1>;
}
return redirect();
For this, I am getting an error: Functions are not valid as a React child. This may happen if you return a Component instead of <Component /> from render. Or maybe you meant to call this function rather than return it.
I also tried using useState:
const [redirectNow, setRedirectNow] = useState(false);
useEffect(() => {
// Some code unrelated to the timeout/redirect
}, []);
const redirect = () => {
setTimeout(() => {
setRedirectNow(false);
}, 5000);
return redirectNow
? <Redirect to={`/login`} />
: <h1>Need to be logged in for that</h1>;
}
return redirect();
but this also gets a different error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons: 1. You might have mismatching versions of React and the renderer (such as React DOM) 2. You might be breaking the Rules of Hooks 3
I understand from reading further that we cannot access the useState stuff from inside an event handler.
Update
I should also add that I am already using useEffect for other things at this point.
Use a state to keep the track whether to redirect or not.
Update the state after the interval.
Redirect upon updating the state.
function RedirectComponent() {
const [shouldRedirect, setShouldRedirect] = React.useState(false);
React.useEffect(() => {
const id = setTimeout(() => {
setShouldRedirect(true);
}, 5000);
return () => clearTimeout(id);
}, []);
if (shouldRedirect) return <Redirect to="/login" />
return <h1>This is the message</h1>
}
I prefer to use custom hook.
In this case, useSetTimeout would work.
import React, { useState, useEffect, useRef } from 'react';
function useSetTimeout(timeoutCallback, seconds) {
const timeoutId = useRef();
useEffect(() => {
timeoutId.current = setTimeout(timeoutCallback, seconds);
return () => clearTimeout(timeoutId.current);
}, [])
}
function YourComponent(){
const [redirectNow, setRedirectNow] = useState(false);
useSetTimeout(()=>{
setRedirectNow(true);
}, 5000);
return redirectNow ? <Redirect to={`/login`} /> : <h1>Need to be logged in for that</h1>;
}

Context Api state is not changing

im trying to call a function called deleteTask inside the Context Provider, from a component that consumes the context using the useContext hook, which deletes a certain item from an array in the state of the context provider, but when i do it, the state of the provider doesnt change at all, i try to follow the problem and the function excecutes but it seems like if it was excecuting in the scope of a copied Provider? Also tried a function to add a task and im having the same issue. I also added a function to set the active task, and i dont know why that one did work, while the others dont. I dont really know whats happening, here is the code, pleeeeease help me:
tasks-context.jsx
import React, { useState } from 'react';
import { useEffect } from 'react';
const dummyTasks = [{
task: {
text: 'hello',
},
key: 0,
isActive: false
},
{
task: {
text: 'hello 2',
},
key: 1,
isActive: false
}];
export const TasksContext = React.createContext({ });
export const TasksProvider = ( props ) => {
const [ tasks, setTasks ] = useState( dummyTasks );
const [ activeTask, setActiveTask ] = useState();
//NOT WORKING
const deleteTask = ( taskToDeleteKey ) =>{
setActiveTask( null );
setTasks( tasks.filter( task => task.key !== taskToDeleteKey ));
};
//THIS ONE WORKS (??)
const handleSelectTask = ( taskToSelect, key ) =>{
setActiveTask( taskToSelect );
const newTaskArray = tasks.map( task => {
if( task.key === key ){
task.isActive = true;
}else{
ficha.isActive = false;
}
return task;
});
setTask( newTaskArray );
};
return ( <TasksContext.Provider
value={{ tasks,
activeTask,
addTask,
deleteTask,
handleSelectTask}}>
{props.children}
</TasksContext.Provider>
);
};
the "main"
Main.jsx
import React from 'react';
import './assets/styles/gestion-style.css';
import './assets/styles/icons.css';
import { TasksProvider } from '../../Context/tasks-context';
import TaskContainer from './components/taskContainer.jsx';
function Main( props ) {
return (
<TasksProvider>
<TaskContainer />
</TasksProvider>
);
}
the task container maps the array of tasks:
TaskContainer.jsx
import React, { useContext, useEffect } from 'react';
import TaskTab from './TaskTab';
import { TasksContext } from '../../Context/tasks-context';
function TaskContainer( props ) {
const { tasks } = useContext( TasksContext );
return (
<div className="boxes" style={{ maxWidth: '100%', overflow: 'hidden' }}>
{tasks? tasks.map( taskTab=>
( <TaskTab task={taskTab.task} isActive={taskTab.isActive} key={taskTab.key} taskTabKey={taskTab.key} /> ))
:
null
}
</div>
);
}
export default TaskContainer;
And the task component from which i call the context function to delete:
TaskTab.jsx
import React, { useContext } from 'react';
import { TasksContext } from '../../Context/tasks-context';
function TaskTab( props ) {
let { task, isActive, taskTabKey } = props;
const { handleSelectTask, deleteTask } = useContext( TasksContext );
const selectTask = ()=>{
handleSelectTask( task, taskTabKey );
};
const handleDelete = () =>{
deleteTask( taskTabKey );
};
return (
<div onClick={ selectTask }>
<article className={`${task.type} ${isActive ? 'active' : null}`}>
<p className="user">{task.text}</p>
<button onClick={handleDelete}>
<i className="icon-close"></i>
</button>
</article>
</div>
);
}
export default TaskTab;
Thanks for the great question!
What is happening here is understandably confusing, and it took me a while to realize it myself.
TL;DR: handleSelectTask in the Provider is being called every time a button is clicked for deleteTask because of event propagation. handleSelectTask isn't using the state that has been modified by deleteTask, even though it's running after it, because it has closure to the initial tasks array.
Quick Solution 1
Stop the event from propagating from the delete button click to the TaskTab div click, which is probably the desired behavior.
// in TaskTab.jsx
const handleDelete = (event) => {
event.stopPropagation(); // stops event from "bubbling" up the tree
deleteTask(taskTabKey);
}
In the DOM (and emulated by React as well), events "bubble" up the tree, so that parent nodes can handle events coming from their child nodes. In the example, the <button onClick={handleDelete}> is a child of the <div onClick={selectTask}>, which means that when the click event is fired from the button, it will first call the handleDelete function like we want, but it will also call the selectTask function from the parent div afterwards, which is probably unintended. You can read more about event propagation on MDN.
Quick Solution 2
Write the state updates to use the intermediary state value at the time they are called.
// in tasks-context.jsx
const deleteTask = ( taskToDeleteKey ) => {
setActiveTask(null);
// use the function version of setting state to read the current value whenever it is run
setTasks((stateTasks) => stateTasks.filter(task => task.key !== taskToDeleteKey));
}
const handleSelectTask = ( taskToSelect, key ) =>{
setActiveTask( taskToSelect );
// updated to use the callback version of the state update
setTasks((stateTasks) => stateTasks.map( task => {
// set the correct one to active
}));
};
Using the callback version of the setTasks state update, it will actually read the value at the time the update is being applied (including and especially in the middle of an update!), which, since the handleSelectTask is called after, means that it actually sees the array that has already been modified by the deleteTask that ran first! You can read more about this callback variant of setting state in the React docs (hooks) (setState). Note that this "fix" will mean that your component will still call handleSelectTask even though the task has been deleted. It won't have any ill-effects, just be aware.
Let's walk through what's happening in a bit more detail:
First, the tasks variable is created from useState. This same variable is used throughout the component, which is totally fine and normal.
// created here
const [ tasks, setTasks ] = useState( dummyTasks );
const [ activeTask, setActiveTask ] = useState();
const deleteTask = ( taskToDeleteKey ) =>{
setActiveTask( null );
// referenced here, no big deal
setTasks( tasks.filter( task => task.key !== taskToDeleteKey ));
};
const handleSelectTask = ( taskToSelect, key ) =>{
setActiveTask( taskToSelect );
// tasks is referenced here, too, awesome
const newTaskArray = tasks.map( task => {
if( task.key === key ){
task.isActive = true;
}else{
task.isActive = false;
}
return task;
});
setTasks( newTaskArray );
};
Where the trouble comes in, is that if both of the functions are trying to update the same state value in the same render cycle, they will both be referencing the original value of the tasks array, even if the other function has attempted to update the state value! In your case, because the handleSelectTask is running after deleteTask, this means that handleSelectTask will update state using the array that hasn't been modified! When it runs, it will still see two items in the array, since the tasks variable won't change until the update is actually committed and everything rerenders. This makes it look like the delete portion isn't functioning, when really its effect is just being discarded since handleSelectTask isn't aware that the delete happened before it.
Lucas, this is not an issue with Context or Provider.
The problem that you are facing is actually a mechanism known as event bubbling where the current handler executes followed by parent handlers.
More info on event bubbling could be found here. https://javascript.info/bubbling-and-capturing.
In your case first, the handleDelete function gets called followed by handleSelect function.
Solution: event.stopPropagation();
Change your handleDelete and handleSelect function to this
const selectTask = () => {
console.log("handle select called");
handleSelectTask(task, taskTabKey);
};
const handleDelete = event => {
console.log("handle delete called");
event.stopPropagation();
deleteTask(taskTabKey);
};
Now check your console and you will find only the handle delete called will print and this would solve your problem hopefully.
If it still doesn't work then do let me know. I will create a codesandbox version for you.
Happy Coding.

Seeking advice: Reason for maximum update depth exceed

I am new to React and GraphQL. Trying to update React state with GraphQL subscription feed but it generates the update depth error.
Here is the simplified code:
import { Subscription } from 'react-apollo';
...
function Comp() {
const [test, setTest] = useState([]);
const Sub = function() {
return (
<Subscription subscription={someStatement}>
{
result => setTest(...test, result.data);
return null;
}
</Subscription>
);
};
const Draw = function() {
return (
<div> { test.map(x => <p>{x}</p>) } </div>
);
};
return (
<div>
<Sub />
<Draw />
<div/>
);
};
export default Comp;
Regular query works fine in the app and the Subscription tag returns usable results, so I believe the problem is on the React side.
I assume the displayed code contains the source of error because commenting out the function "Sub" stops the depth error.
You see what happens is when this part renders
<Subscription subscription={someStatement}>
{
result => setTest(...test, result.data);
return null;
}
</Subscription>
setTest() is called and state is set which causes a re-render, that re-render cause the above block to re-render and setTest() is called again and the loop goes on.
Try to fetch and setTest() in your useEffect() Hook so it does not gets stuck in that re-render loop.
useEffect like
useEffect(() => {
//idk where result obj are you getting from but it is supposed to be
//like this
setTest(...test, result.data);
}, [test] )
Component Like
<Subscription subscription={someStatement} />

How to change useState's state without re-rendering the component, but just its components

How do I change the useState without re-rendering it but just its components? Why I need is because I have some logic that changes the useState in Comp1. If I would re-render Comp1 I would have to recalculate its value. But if the change happens I still would like to re-render its components.
Edit: bigger code snippet.
function Comp1() {
const [user, setUser] = useState({});
firebase.auth().onAuthStateChanged(function (_user) {
if (_user) {
// User is signed in.
setUser(_user);
} else {
setUser({ exists: false });
}
});
return (
<div>
<UserProvider.Provider value={{user, setUser}}>
<Comp2 />
<Comp3 />
</UserProvider.Provider>
</div>
);
}
function Comp2(props) {
const { user, setUser } = useContext(UserProvider);
return (
<div>
{user.exists}
</div>
)
}
function Comp3(props) {
const { user, setUser } = useContext(UserProvider);
return (
<div>
{user.exists}
</div>
)
}
//User Provider
import React from 'react';
const UserProvider = React.createContext();
export default UserProvider;
I took a shot at answering your question. Basically, you want to be able to calculate a value once and have it updated only when the content changes. You can do this with useEffect. The first parameter for useEffect is a function you want to happen on mount/update. It should return a function to run when your function is unmounted (if any). The second argument are the things that determine if useEffect gets ran. I put it to run when user changes value. Hopefully this is enough to get you started down the path of something. You can read more about useEffect in the docs.
function Comp2(props) {
const { user, setUser } = useContext(UserProvider);
const { state, setState } = useState({ value: '' });
useEffect(() => {
console.log(user);
// perform expensive operation
setValue({ value: 'My expensive operation is now stored' });
return () => {
// Do you have anything that you would put in componentWillUnmount()?
// Put that here
};
}, [user]);
console.log(state.value);
return (
<div>
{user.exists}
</div>
)
}
My suggestion is to ditch the useContext hooks as they seem to cause re-renders on components they're attached to.

Categories