I'm just learning React and ran into a problem. I'm making a bunch of counter components that look like this :
The problem is that I have defined the state in each of these counters which is 3 of them and I'd like to pass the value (number) into the parent so I can add it up and display the total count.
Here is my child counter component:
import React, { useState } from "react";
const Counter = () => {
const [count, setcount] = useState(0)
const handleIncrement=()=>{
setcount(count+1);
}
const handleDecrement=()=>{
setcount(count+-1);
}
return (
<div>
<button onClick={()=>{handleIncrement()}}>+</button>
<span>{count}</span>
<button onClick={()=>{handleDecrement()}}>-</button>
</div>
);
};
export default Counter;
And here is the parent which I want to pass my values to so I can add them up and show the total :
import React from 'react'
import Counter from './Counter'
const Counters = () => {
return (
<>
<h3>totalcount:</h3>
<Counter/>
<Counter/>
<Counter/>
</>
)
}
export default Counters
What I tried was to make multiple states but I can't get a good way to make this. I know there's an easy answer for this and I'm making it too complicated. If you guys have other optimization for my code please share.
In React state goes top to bottom. A nested component can update the state of a parent if a function defined in the parent has been passed to it as props. Which means, what you wanna do is not possible as your code is set up. A way to achieve what you are looking for is:
Pass setCounter down to each Counter instance as props, like so:
import React, { useState } from 'react'
import Counter from './Counter'
const Counters = () => {
const [countOne, setCountOne] = useState(0)
const [countTwo, setCountTwo] = useState(0)
const [countThree, setCountThree] = useState(0)
return (
<>
<h3>totalcount: {countOne + countTwo countThree} </h3>
<Counter count = {countOne} setCount = {setCountOne} />
<Counter count = {countTwo} setCount = {setCount} />
<Counter count = {countThree} setCount = {setCountThree} />
</>
)
}
export default Counters
Get setCounter from the props inside Counter and use it where you want:
import React, { useState } from "react";
const Counter = ({count, setCount}) => {
const handleIncrement=()=>{
setCount(count+1);
}
const handleDecrement=()=>{
setCount(count+-1);
}
return (
<div>
<button onClick={()=>{handleIncrement()}}>+</button>
<span>{count}</span>
<button onClick={()=>{handleDecrement()}}>-</button>
</div>
);
};
export default Counter;
Related
So I'm trying to learn how to use context since I'm building a dating app for dogs just as a fun project and will have to pass the current user throughout the whole application.
Tho i don't seem to grasp how to use context with typescript. I have a component called HomeView and would like to view counter and possibly set the counter with the usestate function setCounter.
When I try to log the counter in my HomeView components all I get is undefined even tho the value is set to 0. I should be able to log 0 onto the screen in my consumer. What am I missing?
Contex.tsx
import { createContext, useState } from 'react';
type ContextType = {
setCounter: React.Dispatch<React.SetStateAction<number>>;
counter: number;
};
export const CounterContext = createContext<ContextType>({} as ContextType);
export const CounterContextProvider: React.FC = ({ children }) => {
const [counter, setCounter] = useState(0);
return <CounterContext.Provider value={{ counter, setCounter }}>{children}</CounterContext.Provider>;
};
HomeView.tsx
export const HomeView = () => {
const context = useContext(CounterContext);
console.log(context.counter); // value is undefined
return (
<div className='signInWrapper'>
<SignIn />
<CounterContextProvider>
{context.counter} // Nothing is shown onto the screen
</CounterContextProvider>
</div>
);
};
Since you are retrieving the context in a component which is not nested in the CounterContext.Provider itself you are getting undefined.
You should nest your entire application in the CounterContextProvider in order to access the state globally.
You can do this in your App.js file:
function App() {
return (
<CounterContextProvider>
...
<HomeView/>
</CounterContextProvider>
After this you should be able to access the context in your HomeView component:
export const HomeView = () => {
const { counter, setCounter } = useContext(CounterContext);
return (
<div className='signInWrapper'>
<SignIn />
Your counter value is: {counter}
</div>
);
};
I am trying on React.js but what I would like does not work. Here is my code :
import React, { useState } from 'react';
function App() {
const [count, setCount] = useState(0);
setCount(count + 1);
return (
<div>
<p>value of count : {count} fois</p>
</div>
);
}
export default App;
I got :
Too many re-renders. React limits the number of renders to prevent an infinite loop.
You can see the error there : https://codesandbox.io/s/nostalgic-engelbart-tzbxv?file=/src/App.js:0-244
Thank you for your help !
You're creating an infinite loop. Every time state updates, the component is re-rendered. The component is rendering, then it's calling setCount the state updates, then the component renders, and on and on.
You can use useEffect to update the state after it's rendered the first time.
useEffect(() => {
setCount(count + 1)
},[])
I'm not sure what the point of this would be though, since you can just pass the initial value into useState
You are calling setCount in the body of the component, which is essentially the render function if thinking in a class based component. Therefore it is causing an infinite loop of setting a new value, rerender, setting a new value, rerender, and so on.
You should only be calling setCount from some sort of event, e.g. a button click or an effect, e.g. using useEffect
Whenever you change the state then rerender is triggered.
In your case, when you are calling setCount on the count and the count state is changing and it is triggering rerender and this cycle is continued. That's why this error.
Try this example, where the setCount is called when only once and the value is being changed as it supposed to be.
import React, { useState, useEffect } from "react";
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
setCount(count + 1);
}, []);
return (
<div>
<p>value of count : {count} fois</p>
</div>
);
}
export default App;
Here is another example where count is increased every time the button is pressed:
import React, { useState, useEffect } from "react";
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
setCount(count + 1);
}, []);
return (
<div>
<p>value of count : {count} fois</p>
<button
onClick={() => {
setCount(count + 1);
}}
>
Increase
</button>
</div>
);
}
export default App;
Output:
Codesandbox Example
The reason you are getting the error is that calling setCount(count + 1); will increase the state count forever. Try putting it on a condition, for example in this codesandbox/code below, the state count is increased by 1 every time you click the button. You could also increase state if (count < 10), inside of a setInterval or under a wide variety of conditions. Just not infinitely! ;)
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const [count, setCount] = useState(0);
const increaseCount = () => {
setCount(count + 1);
};
return (
<div>
<p>value of count : {count} fois</p>
<button onClick={increaseCount}>start count</button>
</div>
);
}
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
I create a context and a provider as below. As you can see, I use useState() within my provider (for state) along with functions (all passed within an object as the value prop, allows for easy destructuring whatever I need in child components).
import React, { useState, createContext } from "react";
const CountContext = createContext(null);
export const CountProvider = ({ children }) => {
const [count, setCount] = useState(0);
const incrementCount = () => {
setCount(count + 1);
};
const decrementCount = () => {
setCount(count - 1);
};
return (
<CountContext.Provider value={{ count, incrementCount, decrementCount }}>
{children}
</CountContext.Provider>
);
};
export default CountContext;
I wrap my app within such a provider(s) at a higher location such as at index.js.
And consume the state using useContext() as below.
import React, { useContext } from "react";
import CountContext from "../contexts/CountContext";
import Incrementer from "./Incrementer";
import Decrementer from "./Decrementer";
const Counter = () => {
const { count } = useContext(CountContext);
return (
<div className="counter">
<div className="count">{count}</div>
<div className="controls">
<Decrementer />
<Incrementer />
</div>
</div>
);
};
export default Counter;
Everything is working just fine, and I find it easier to maintain things this way as compared to some of the other methods of (shared) state management.
CodeSandbox: https://codesandbox.io/s/react-usecontext-simplified-consumption-hhfz6
I am wondering if there is a fault or flaw here that I haven't noticed yet?
One of the key differences with other state management tools like Redux is performance.
Any child that uses a Context needs to be nested inside the ContextProvider component. Every time the ContextProvider state changes it will render, and all its (non-memoized) children will render too.
In contrast, when using Redux we connect each Component to the store, so each component will render only if the part of the state it is connect to changes.
I've created 2 components C1 and C2 and one custom hook useCounter.
C1 displays "count" property of useCounter hook.
C2 displays "count" property and also increment or decrement it on button click.
Current behavior: When the "count" is changed, the updated value is displayed only in C2 and not in C1.
Expected behavior: Both components should re-render on the "count" update.
Please let me know if I'm missing something.
PS: I've already done this using Context API and redux. Just want to know if the same behavior could be achieved using custom hooks :)
Codesandbox link: Custom hooks demo
import { useState } from "react";
function useCounter() {
const [count, setCount] = useState(0);
const updateCounter = newVal => {
setCount(newVal);
};
//return [count, useCallback( (newVal) => setCount(newVal))];
return [count, updateCounter];
}
export default useCounter;
You might want to use Context API to share the same instance, as the custom hook useCounter will assign a new instance of count on mount:
export const CounterContext = React.createContext();
function App() {
const counterController = useCounter();
return (
<CounterContext.Provider value={counterController}>
<div className="App">
<h1>App Component</h1>
<hr />
<C1 />
<hr />
<C2 />
</div>
</CounterContext.Provider>
);
}
// Use context
function C1() {
const [count] = useContext(CounterContext);
return (
<div>
Component 1 <br />
Count: {count}
</div>
);
}
Additionally, you can use a library like reusable:
const useCounter = createStore(() => {
const [counter, setCounter] = useState(0);
return {
counter,
increment: () => setCounter(prev => prev + 1)
}
});
const Comp1 = () => {
const something = useCounter();
}
const Comp2 = () => {
const something = useCounter(); // same something
}
Hey I am totally agree with #RVRJ's explanations. So what happens when you import any hooks it will create new object of that hook. Let's suppose if you import same hook in two different files that means you are creating two difference object of that hook.
Here I have tried to solve your problem using hooks only, but I am importing hook only once and passed its object to child component <C1 /> and <C2 />.
Here is example how I created single object of useCounter hook
import React from "react";
import C1 from "./components/C1";
import C2 from "./components/C2";
import useCounter from "./hooks/useCounter";
function App() {
const [count, updateCount] = useCounter(); // <<-- created one object
return (
<div className="App">
<h1>App Component</h1>
<hr />
<C1 count={count} updateCount={updateCount} /> {/* passing values ad props */}
<hr />
<C2 count={count} updateCount={updateCount} /> {/* passing values ad props */}
</div>
);
}
export default App;
and now you can access count and updateCount as props in every child.
Here is C1 component after change
// C1 component
import React from "react";
function C1({ count }) { {/* <-- access count as props */}
return (
<div>
Component 1 <br />
Count: {count}
</div>
);
}
export default C1;
And here is your C2 component
// C2 component
import React from "react";
function C3({ count, updateCount }) { {/* <-- access count and as updateCount props */}
const handleIncr = () => {
updateCount(count + 1);
};
const handleDecr = () => {
updateCount(count - 1);
};
return (
<div>
Component 2 <br />
<button onClick={handleIncr}>Increment</button>
<button onClick={handleDecr}>Decrement</button>
<br />
<br />
<br />
Count: {count}
</div>
);
}
export default C3;
Here is updated and working solutions of your problem https://codesandbox.io/s/romantic-fire-b3exw
Note: I don't know what is use case of using hooks for same state values, so I still recommend you should use redux for sharing states between components.
Do two components using the same Hook share state? No. Custom Hooks are a mechanism to reuse stateful logic (such as setting up a subscription and remembering the current value), but every time you use a custom Hook, all state and effects inside of it are fully isolated.
Refer point #3 in Using a Custom Hook
They’re not a way to share state — but a way to share stateful logic. We don’t want to break the top-down data flow!
Reference: Making Sense of React Hooks