How to render stateHook result from the function call in ReactJS - javascript

I'm trying to get the result of stateHook and render it properly
//import React from 'react';
import React, {useState,useEffect} from 'react';
import {DashboardLayout} from '../components/Layout';
const ProjectsPage = () => {
function GetCount() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
return (
<DashboardLayout>
<h2>Projects Page {GetCount}</h2>
</DashboardLayout>
)
}
export default ProjectsPage;
While rendering a function call in ReactJS it throws this error
Warning: 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.
at h2
at div
at section
at main
at div
at div
at main
at div
at BodyWrapper (http://localhost:3000/static/js/main.chunk.js:210:3)
at DashboardLayout (http://localhost:3000/static/js/main.chunk.js:338:3)
at ProjectsPage
at Route (http://localhost:3000/static/js/vendors~main.chunk.js:39794:29)
at Switch (http://localhost:3000/static/js/vendors~main.chunk.js:39996:29)
at Router (http://localhost:3000/static/js/vendors~main.chunk.js:39429:30)
at BrowserRouter (http://localhost:3000/static/js/vendors~main.chunk.js:39049:35)
at Routes
at App
And the UI remains blank,

The problem the error message is referring to is here:
<h2>Projects Page {GetCount}</h2>
GetCount is a function (specifically, a component function). You're using it (not calling it) there. You want to use the component, like so:
<h2>Projects Page <GetCount/></h2>
I'd also suggest adding the missing dependency on the useEffect hook:
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]);
// ^^^^^^^
You don't absolutely need it if count is the only state item and the component has no properties, but it's best practice to list the effect's dependencies so it isn't called too often.

You are trying to render:
<DashboardLayout>
<h2>Projects Page {GetCount}</h2>
</DashboardLayout>
However, GetCount is a function, hence:
Functions are not valid as a React child.
What you should do is:
<DashboardLayout>
<h2>Projects Page</h2>
<GetCount />
</DashboardLayout>

Related

Why only first time React function component getting props

React throw an error when we try to update the state on an unmounted component.So When I test react component for that I am getting errors on the first render only.
I made a component that enable child component based on click. And child component have button which updates state after some settimeout which throw react warning
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
Which is perfectly valid. But to overcome this I am passing enable props from the parent component based on that there is a condition just before setTimeout in the child component. So why does it throw an error the first time only?
To reproduce
Click on the child component button which is false and click on parent component button enable which unmount the child component.
**My question is why react throw an error on the first time only ? And why it is working fine on second time **
Parent component
import { useState } from "react";
import "./styles.css";
import { Test } from "./Test";
export default function App() {
const [state, setstate] = useState(true);
const changeState = () => {
setstate(!state);
};
return (
<div className="App">
<button onClick={changeState}>enable </button>
{state && <Test enable={state} />}
</div>
);
}
Child Component
import React, { useState } from "react";
export const Test = (props) => {
const [state, setstate] = useState(false);
const fetchData = () => {
setstate(!state);
if (props.enable) {
setTimeout(() => {
setstate(false);
}, 1000);
}
};
return (
<>
<button onClick={fetchData}> {`${state}`}</button>
</>
);
};
Codesandbox link to test
Nice track, Just you are missing a minor point, when you write a state thats needed some time to execute and the same time we can visit the flow again and again base on any action, then we need to clear old subscription before go to new one...
For example, in your code here, you update state flow, but the state flow is register a new subscription every time we visit a component with valid props and click on button, so that, prev execution may still work when you trigger new event, so simply, what we need to do unmounted old subscription and we can do that by this for your case:
import React, { useState, useEffect } from "react";
export const Test = (props) => {
const [state, setstate] = useState(false);
useEffect(() => {
if (props.enable) {
const timer = setTimeout(() => {
setstate(false);
}, 1000);
return () => clearTimeout(timer);
}
}, [state, props.enable]);
const fetchData = () => {
setstate((prev) => !prev);
};
return (
<>
<button onClick={fetchData}> {`${state}`}</button>
</>
);
};
Look at code above, simply we add code need to cleanup in effect which its look to my state and prop, now when I click on button, the effect will trigger, if we do that again, the clearTime will work for prev subscription and then add new one and so on...
Notes:
In your case we can remove function and use setState direct on your button.
Prefer to use useCallBack in your function like const fetchData = useCAllback...
You can use setstate((prev) => !prev); its will be work as snapshot, and its usefull when you depends on old value..but may it not needed in some cases too, but just to know about this feature.
Update 1:
What is Subscription:
You can say the subscription represents a disposable resource, such as the execution of an Observable. A Subscription has one important method, unsubscribe, that takes no argument and just disposes the resource held by the subscription, in another word, you can say yes, any async task or any job will be invoke to react life-cycle state and its needed to observe changes, then you talk about subscribe, like API or time out or time interval and so on, any of these action thats need to clear prev subscribe (stop observer - unsubscribe) to prevent any memory leek and clear memory to keep state flow safe and prevent unneeded reredner.

React & Firebase - Appending component with Firebase onSnapshot

Currently, I'm using a button with an onClick function to append a JSX component to the UI every time it's been pressed. I'm also using a useEffect with some firebase database logic which is an onSnapshot to get live reads on the data to display the information onto the UI.
The problem is when a user refreshes the page the onClick function that appends the JSX is deleted from the state and doesn't display the firebase data onto the UI. How would I go about sorting this problem out?
Code:
import React, { useEffect, useState } from "react";
import DashboardBody from "./DashboardComponents/DashboardBody";
import db from "../../firebase";
function Test() {
const [exercise, setExercise] = useState([]);
const [count, setCount] = useState(0);
useEffect(() => {
db.collection("users").onSnapshot((snapshot) => {
setExercise(
snapshot.docs.map((doc) => ({
id: doc.id,
exercise: doc.data().exercise,
}))
);
});
return;
}, []);
return (
<div>
{[...Array(count)].map((count, index) => (
<DashboardBody key={index} exercise={exercise} />
))}
<div>
<button onClick={() => setCount(count + 1)}>Click</button>
</div>
</div>
);
}
export default Test;
The count variable is stored in the component's state and is incremented whenever the button is clicked. When the page reloads (or the component unmounts and mounts again), the state variable is lost. To preserve the state, you would have to store it in the database (or in the browser's storage) so that you can fetch it when the component mounts again.

Trigger a function in another component on click a button from a different component in React

I am trying to trigger a start function in a different componentB when I click the start button in componentA
Note: Both components are neither parent to child components
Component A
import React from "react"
function ComponentA(props) {
return (
<div>
<button>Start</button>
</div>
)
}
export default ComponentA;
Component B
import React from "react";
function ComponentB(props) {
const [isStarted, setStarted] = React.useState(false);
const start = () => setStarted(true);
return <div>{isStarted ? "Starting..." : "Not Starting.."}</div>;
}
export default ComponentB;
One way you could do it is by creating a callback prop on ComponentA, changing the state of the parent component of ComponentA and passing it to ComponentB via a prop and capture that prop change with a useEffect.
Example:
Parent
function Parent(){
const [started, setStarted] = useState(false)
return(
<div>
<ComponentA onClick={() => setStarted(true)}/>
<ComponentB started={started}/>
</div>
)
}
ComponentA
function ComponentA({onClick}){
return(
<div>
<button onClick={() => onClick()}/>
</div>
)
}
ComponentB
function ComponentB({started}) {
const [isStarted, setStarted] = React.useState(started);
useEffect(() => {
setStarted(started)
}, [started])
return <div>{isStarted ? "Starting..." : "Not Starting.."}</div>;
}
Another way would be using useContext:
https://reactjs.org/docs/hooks-reference.html#usecontext
https://reactjs.org/docs/context.html
Honestly, I am a bit lazy to also include an example which is in my opinion worse. Here is an example that uses useContext that might be useful.
https://stackoverflow.com/a/54738889/7491597

Conditional rendering in React Native with global variable

I know this is horrible convention, but I'm trying to quickly conditionally render screens in my React Native app with global variables (so no redux):
App.js:
if (global.clickStatus !== 'clicked') {
return <Screen1 />;
}
return <Screen2 />;
The app begins on Screen1, where there is a button that makes global.clickStatus = 'clicked'. When this is clicked, I want Screen2 to render. The problem is, the global.clickStatus doesn't seem to update on my App.js (even though global.clickStatus is changed, it still renders Screen1.
How can I get it to update?
I believe in <App /> component because it is a function component you can introduce a state if your button is clicked. Then with clicked state you can manipulate which component to show.
Similarly like the following - obviously this is a simplified example:
const App = () => {
const [clicked, setClicked] = useState(false);
return <>
<div onClick={() => setClicked(true)}>Click me</div>
{ clicked ? <Screen2 /> : <Screen1 /> }
</>
}
Suggested read is Using the State Hook.
The app begins on Screen1, where there is a button that makes global.clickStatus = 'clicked'
When you click the button, you did not set any state for App.js component => no re-render action is made.
I just assume the button is in Screen 1. Try code below:
import React from "react";
import "./styles.css";
export default function App() {
// Create a state
const [renderIndex, setRenderIndex] = useState(new Date().getTime())
if (global.clickStatus !== 'clicked') {
// Assume you have a button in Screen1
// Pass a callback function from this component to Screen1
// When button in Screen1 is clicked, call this callback function to update renderIndex => App component will re-render
return <Screen1 callBack={() => setRenderIndex(new Date().getTime())}/>;
}
return <Screen2 />;
}

useEffect hook not triggered when state is updated somewhere else

I`m having some problems trying to listen to state changes in this application. Basically I was expecting a useEffect hook to be fired after some state changed, but nothing at all is happening.
This is what I got
index.jsx
// this is a simplification.
// I actually have a react-router-dom's Router wrapping everything
// and App is a Switch with multiple Route components
ReactDOM.render(
<Provider>
<App>
</Provider>
, document.getElementById('root'));
useSession.jsx
export const useSession = () => {
const [session, setSession] = useState(null)
const login = useCallback(() => {
// do something
setSession(newSession)
})
return {
session,
setSession,
login
}
}
Provider.jsx
const { session } = useSession();
useEffect(() => {
console.log('Print this')
}, [session])
// more code ...
App.jsx
export function Login() {
const { login } = useSession();
return <button type="button" onClick={() => { login() }}>Login</button>
}
Well I have this Parent component Provider watching the session state, but when it is updated the useEffect of the provider is never called.
The useEffect is fired only if the setSession is called in the same hook/method. For example, if I import the setSession in the Provider and use it there, the useEffect will be fired; Or if I add a useEffect in the useSession method, it is gonna be fired when login updates the state.
The callback of useEffect is called but only once, when the component is mounted, but not when the state is changed.
How can I achieve this behavior? Having the Provider's useEffect fired whenever session is updated?
Thanks in advance!
I think this is just a bit of misunderstanding of how custom hooks work.Every instance of the component has its own state. Let me just show a simple example illustrating this.
function App () {
return (
<div>
<ComponentA/>
<ComponentB/>
<ComponentC/>
<ComponentD/>
</div>
)
}
function useCounter() {
const [counter, setCounter] = React.useState(0);
function increment() {
setCounter(counter+1)
}
return {
increment, counter, setCounter
}
}
function ComponentA() {
const { counter, increment }= useCounter()
return (
<div>
<button onClick={()=>increment()}>Button A</button>
ComponentA Counter: {counter}
</div>
)
}
function ComponentB() {
const { counter, increment }= useCounter()
return (
<div>
<button onClick={()=>increment()}>Button B</button>
ComponentB Counter: {counter}
</div>
)
}
function ComponentC() {
const { counter }= useCounter();
return (
<div>
ComponentC Counter: {counter}
</div>
)
}
function ComponentD() {
const [toggle, setToggle] = React.UseState(false);
const { counter }= useCounter();
React.useEffect(() => {
setInterval(()=>{
setToggle(prev => !prev);
}, 1000)
})
return (
<div>
ComponentD Counter: {counter}
</div>
)
}
From the above code if you can see that incrementing count by clicking Button Awill not affect the count instance of ComponentB.This is because every instance of the component has its own state. You can also see that clicking either buttons won't trigger ComponentC to rerender since they don't share the same instance. Even if i trigger rerender every one second like in Component D thus invoking useCounter the counter in ComponentD remains 0.
Solution
However there are multiple ways of making components share/listen to same state changes
You can shift all your state i.e [session state] to the Provider component and make it visible to other components by passing it via props.
You can move state to a global container Redux or simply use Context Api + UseReducer Hook here is an example
But since you are dealing with auth and session management, I suggest you persist the session state in local storage or session storage, and retrieve it whenever you need it. Hope that helped

Categories