How to persist counter value when the state changes? - reactjs

In this component when I change the state, it reassigns counter=1 which I do not want. I just want that it assigns counter=1 only once and then does not assign it again during re-render.
And, I do not want to use useState for counter to solve this issue.
function App() {
const [value, setValue] = useState(100);
let counter = 1;
return (
<div>
<button
onClick={() => {
setValue(value - 1);
counter++;
}}
>
click me
</button>
</div>
);
}
export default App;

You can use useRef:
useRef returns a mutable ref object whose .current property is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component.
useRef doesn’t notify you when its content changes. Mutating the
.current property doesn’t cause a re-render.
function App() {
const [value, setValue] = useState(100)
const counter = useRef(1)
return (
<div>
<button
onClick={() => {
setValue(value - 1)
counter.current++
}}
>
click me
</button>
</div>
)
}

You can persist the counter with localStorage:
import { useState } from "react";
function App() {
const [value, setValue] = useState(100);
let counter = localStorage.getItem('counter') || 1;
return (
<div>
<button
onClick={() => {
setValue(value - 1);
localStorage.setItem('counter', ++counter);
}}
>
click me
</button>
</div>
);
}
export default App;

Related

useRef() does not redraw the value

I have a useRef hook and two components. In one component, I increase the value on click by 1 unit, and in the second component, I draw the value. I pass the value itself through useContext.
Now the problem is that the value is not being redrawn. How can this be fixed?
export const ContactContext = React.createContext();
function App() {
const countItem = useRef(1);
const value = { countItem };
return (
<ContactContext.Provider value={value}>
<div>
<AddValue />
</div>
<div>
<Logo />
</div>
</ContactContext.Provider>
);
}
const AddValue = () => {
const { countItem } = useContext(ContactContext);
const addItemHandler = () => {
countItem.current = countItem.current + 1;
};
return (
<>
<div>
<button
onClick={addItemHandler}
>
<img src="plus.svg" alt="plus logo" />
</button>
</div>
</>
);
};
function Logo() {
const { countItem } = useContext(ContactContext);
return (
<p data-testid="statistics">
{`Count of channels: ${countItem.current}`} <br />
</p>
);
}
useRef wont cause components in React to rerender
function App() {
const [countItem, setCountItem] = useState(1)
const value = { countItem, setCountItem };
In AddValue
const AddValue = () => {
const { countItem, setCountItem } = useContext(ContactContext);
const addItemHandler = () => {
setCountItem(c => c +1)
};
Reading the new React docs for state management will help
Hope it helps
Replace useRef with useState.
useRef update the value but does not rerender.

className statement not re-rending when useState is set

When I click a "Thread"/button handleClick get's called and the currentIndex is updated/set which triggers the button's className ternary statement to execute as true. However, as there are many <li> elements, the other elements do not re-render className to the false statement. currentIndex should not == thread.id for those elements.
ThreadItem component
import { useState } from 'react'
const ThreadItem = ({ thread, changeThread }) => {
const [currentIndex, setCurrentIndex] = useState('')
// On thread click
const handleClick = (threadId) => {
setCurrentIndex(threadId)
}
return (
<li className="chat-item">
<form onSubmit={changeThread}>
<button className={currentIndex == thread.id ? 'side-menu-links-focus threads' : 'side-menu-links threads'} onClick={() => handleClick(thread.id)}>{`#${thread.threadType}`}</button>
</form>
</li>
)
}
export default ThreadItem
Parent ThreadList component
import ThreadItem from './ThreadItem'
const ThreadList = ({ threads, changeThread }) => {
return (
<ul className='chat-list'>
{threads.map((thread) => (
<ThreadItem
key={thread.id}
thread={thread}
changeThread={changeThread}
/>
))}
</ul>
)
}
export default ThreadList
If I understand your problem correctly, you want to click a button, and have a single ThreadItem to change className.
The problem I see is that you have added useState to every ThreadItem. Instead, you should move the state above to the ThreadList component.
This way, there is only one instance of state for the entire list of items. Simply pass the active prop and an onClick handler to each ThreadItem.
ThreadList
import React, { useState } from 'react';
import ThreadItem from './ThreadItem';
const ThreadList = ({ threads, changeThread }) => {
const [currentIndex, setCurrentIndex] = useState('');
// On thread click
const handleClick = (threadId) => {
setCurrentIndex(threadId);
};
return (
<ul className="chat-list">
{threads.map((thread) => (
<ThreadItem
key={thread.id}
thread={thread}
changeThread={changeThread}
isActive={currentIndex === thread.id}
onClick={() => handleClick(thread.id)}
/>
))}
</ul>
);
};
export default ThreadList;
ThreadItem
import React from 'react';
const ThreadItem = ({ thread, changeThread, isActive, onClick }) => {
return (
<li className="chat-item">
<form onSubmit={changeThread}>
<button
className={isActive ? 'side-menu-links-focus threads' : 'side-menu-links threads'}
onClick={onClick}
>{`#${thread.threadType}`}</button>
</form>
</li>
);
};
export default ThreadItem;

How to add multiple events in one tag?

I'm making a counting timer which is described below with this react functional component
import {useEffect, useState, useRef} from 'react'
function Content() {
const [countdown, setCountdown] = useState(10)
const [show, setShow] = useState(true)
const ref = useRef()
function handleStart() {
ref.current = setInterval(() => {
setCountdown(prev => prev - 1)
}, 1000)
}
function handleStop() {
clearInterval(ref.current)
}
return (
<div>
<h2 style={{padding: 20}}>Time remaining: {countdown}</h2>
<button onClick={handleStart}>Start</button>
<button onClick={handleStop}>Stop</button>
</div>
)
}
export default Content;
How do I hide these two buttons after clicking one of the two.
Assuming show is the variable to control whether the buttons are visible or not.
<div>
<h2 style={{padding: 20}}>Time remaining: {countdown}</h2>
{show && <>
<button onClick={() => {
setShow(false)
handleStart()
}}>Start</button>
<button onClick={() => {
setShow(false)
handleStop()
}}>Stop</button>
</>}
</div>
React children need to return one element, so you can either wrap it in a div, or an empty element, <> </>, so you can return multiple nodes without adding a div, span, etc.
show && <></> means if show is true, the right-hand side will render, otherwise, it won't be rendered.
First, you have to introduce new state variable, you need one ror the start btn and another for the stop btn.
You have to setShow to false on either click and render the buttons conditionally depending on show variable:
const [countdown, setCountdown] = useState(10)
const [showStart, setShowStart] = useState(true)
const [showStop, setShowStop] = useState(true);
const ref = useRef()
function handleStart() {
setShowStart(false);
ref.current = setInterval(() => {
setCountdown(prev => prev - 1)
}, 1000)
}
function handleStop() {
setShowStop(false);
clearInterval(ref.current)
}
return (
<div>
<h2 style={{padding: 20}}>Time remaining: {countdown}</h2>
{showStart && <button onClick={handleStart}>Start</button>}
{showStop && <button onClick={handleStop}>Stop</button>}
</div>
)
Hope the Below Code Solver Your Problem
import React, { useEffect, useState, useRef } from 'react';
function Example() {
const [countdown, setCountdown] = useState(10);
const [show, setShow] = useState(true);
const ref = useRef();
function handleStart() {
setShow(!show);
ref.current = setInterval(() => {
setCountdown((prev) => prev - 1);
}, 1000);
}
function handleStop() {
setShow(!show);
clearInterval(ref.current);
}
return (
<div>
<h2 style={{ padding: 20 }}>Time remaining: {countdown}</h2>
{show && (
<div>
<button onClick={handleStart}>Start</button>
<button onClick={handleStop}>Stop</button>
</div>
)}
</div>
);
}
export default Example;

React useContext cannot make simple counter work

I was trying to make a simple example illustrating how useContext work.
I have started with this sandbox code:
https://codesandbox.io/s/react-typescript-usecontext-lama-1v7wd
The issue is that the component Counter does not update and rerender when I click the button.
My index
import React, { useContext } from 'react'
import { MyContextProvider, MyContext } from './Context'
import { render } from 'react-dom'
const MyCounter = () => {
const context = useContext(MyContext)
const { counter } = context
const { setCounter } = context
return (
<div>
Valeur du compteur : {counter}
<br />
<button onClick={() => setCounter(counter - 1)} type="button">
-1
</button>
<button onClick={() => setCounter(counter + 1)} type="button">
+1
</button>
<br />
<button onClick={() => setCounter(0)} type="button">
RàZ
</button>
</div>
)
}
const rootElement = document.getElementById('root')
render(
<MyContextProvider>
<MyCounter />
</MyContextProvider>,
rootElement
)
My context:
type MyContextProps = {
counter: number
setCounter: Dispatch<SetStateAction<number>>
}
const MyContext = createContext({} as MyContextProps)
const MyContextProvider: React.FunctionComponent = (props) => {
const [counter, setCounter] = useState<number>(0)
return (
<MyContext.Provider
value={{
counter: 0,
setCounter: setCounter,
}}
>
{props.children}
</MyContext.Provider>
)
}
export { MyContext, MyContextProvider }
It's got to be something elementary, but I just can't see it.
Just a small error.
in your context, you have set your counter to be always zero. Change this to be counter state and your problem should be resolved.
const MyContextProvider: React.FunctionComponent = (props) => {
const [counter, setCounter] = useState<number>(0)
return (
<MyContext.Provider
value={{
counter: counter, //<-- HERE.
setCounter: setCounter,
}}
>
{props.children}
</MyContext.Provider>
)
}
As a suggestion, consider using function to get the latest state of your value when setting the next value using the setCounter function, if the next value is dependent on the previous value.
The mutation function from useState can also accept a callback function, which provides the latest current state and should return the next state value based on the previous value. This is especially helpful if setting state is in an async operation preventing stale closures.
(prevValue) => nextValue
<button onClick={() => setCounter(prevValue => prevValue - 1)} type="button">
and
<button onClick={() => setCounter(prevValue => prevValue + 1)} type="button">

React Hook : Send data from child to parent component

I'm looking for the easiest solution to pass data from a child component to his parent.
I've heard about using Context, pass trough properties or update props, but I don't know which one is the best solution.
I'm building an admin interface, with a PageComponent that contains a ChildComponent with a table where I can select multiple line. I want to send to my parent PageComponent the number of line I've selected in my ChildComponent.
Something like that :
PageComponent :
<div className="App">
<EnhancedTable />
<h2>count 0</h2>
(count should be updated from child)
</div>
ChildComponent :
const EnhancedTable = () => {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Click me {count}
</button>
)
};
I'm sure it's a pretty simple thing to do, I don't want to use redux for that.
A common technique for these situations is to lift the state up to the first common ancestor of all the components that needs to use the state (i.e. the PageComponent in this case) and pass down the state and state-altering functions to the child components as props.
Example
const { useState } = React;
function PageComponent() {
const [count, setCount] = useState(0);
const increment = () => {
setCount(count + 1)
}
return (
<div className="App">
<ChildComponent onClick={increment} count={count} />
<h2>count {count}</h2>
(count should be updated from child)
</div>
);
}
const ChildComponent = ({ onClick, count }) => {
return (
<button onClick={onClick}>
Click me {count}
</button>
)
};
ReactDOM.render(<PageComponent />, document.getElementById("root"));
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>
You can create a method in your parent component, pass it to child component and call it from props every time child's state changes, keeping the state in child component.
const EnhancedTable = ({ parentCallback }) => {
const [count, setCount] = useState(0);
return (
<button onClick={() => {
const newValue = count + 1;
setCount(newValue);
parentCallback(newValue);
}}>
Click me {count}
</button>
)
};
class PageComponent extends React.Component {
callback = (count) => {
// do something with value in parent component, like save to state
}
render() {
return (
<div className="App">
<EnhancedTable parentCallback={this.callback} />
<h2>count 0</h2>
(count should be updated from child)
</div>
)
}
}
To make things super simple you can actually share state setters to children and now they have the access to set the state of its parent.
example:
Assume there are 4 components as below,
function App() {
return (
<div className="App">
<GrandParent />
</div>
);
}
const GrandParent = () => {
const [name, setName] = useState("i'm Grand Parent");
return (
<>
<div>{name}</div>
<Parent setName={setName} />
</>
);
};
const Parent = params => {
return (
<>
<button onClick={() => params.setName("i'm from Parent")}>
from Parent
</button>
<Child setName={params.setName} />
</>
);
};
const Child = params => {
return (
<>
<button onClick={() => params.setName("i'm from Child")}>
from Child
</button>
</>
);
};
so grandparent component has the actual state and by sharing the setter method (setName) to parent and child, they get the access to change the state of the grandparent.
you can find the working code in below sandbox,
https://codesandbox.io/embed/async-fire-kl197
IF we Have Parent Class Component and Child function component this is how we going to access child component useStates hooks value :--
class parent extends Component() {
constructor(props){
super(props)
this.ChildComponentRef = React.createRef()
}
render(){
console.log(' check child stateValue: ',
this.ChildComponentRef.current.info);
return (<> <ChildComponent ref={this.ChildComponentRef} /> </>)
}
}
Child Component we would create using
React.forwardRef((props, ref) => (<></>))
. and
useImperativeHandle(ref, createHandle, [deps])
to customizes the instance value that is exposed to parent components
const childComponent = React.forwardRef((props, ref) => {
const [info, setInfo] = useState("")
useEffect(() => {
axios.get("someUrl").then((data)=>setInfo(data))
})
useImperativeHandle(ref, () => {
return {
info: info
}
})
return (<> <h2> Child Component <h2> </>)
})
I had to do this in type script. The object-oriented aspect would need the dev to add this callback method as a field in the interface after inheriting from parent and the type of this prop would be Function. I found this cool!
Here's an another example of how we can pass state directly to the parent.
I modified a component example from react-select library which is a CreatableSelect component. The component was originally developed as class based component, I turned it into a functional component and changed state manipulation algorithm.
import React, {KeyboardEventHandler} from 'react';
import CreatableSelect from 'react-select/creatable';
import { ActionMeta, OnChangeValue } from 'react-select';
const MultiSelectTextInput = (props) => {
const components = {
DropdownIndicator: null,
};
interface Option {
readonly label: string;
readonly value: string;
}
const createOption = (label: string) => ({
label,
value: label,
});
const handleChange = (value: OnChangeValue<Option, true>, actionMeta: ActionMeta<Option>) => {
console.group('Value Changed');
console.log(value);
console.log(`action: ${actionMeta.action}`);
console.groupEnd();
props.setValue(value);
};
const handleInputChange = (inputValue: string) => {
props.setInputValue(inputValue);
};
const handleKeyDown: KeyboardEventHandler<HTMLDivElement> = (event) => {
if (!props.inputValue) return;
switch (event.key) {
case 'Enter':
case 'Tab':
console.group('Value Added');
console.log(props.value);
console.groupEnd();
props.setInputValue('');
props.setValue([...props.value, createOption(props.inputValue)])
event.preventDefault();
}
};
return (
<CreatableSelect
id={props.id}
instanceId={props.id}
className="w-100"
components={components}
inputValue={props.inputValue}
isClearable
isMulti
menuIsOpen={false}
onChange={handleChange}
onInputChange={handleInputChange}
onKeyDown={handleKeyDown}
placeholder="Type something and press enter..."
value={props.value}
/>
);
};
export default MultiSelectTextInput;
I call it from the pages of my next js project like this
import MultiSelectTextInput from "../components/Form/MultiSelect/MultiSelectTextInput";
const NcciLite = () => {
const [value, setValue] = useState<any>([]);
const [inputValue, setInputValue] = useState<any>('');
return (
<React.Fragment>
....
<div className="d-inline-flex col-md-9">
<MultiSelectTextInput
id="codes"
value={value}
setValue={setValue}
inputValue={inputValue}
setInputValue={setInputValue}
/>
</div>
...
</React.Fragment>
);
};
As seen, the component modifies the page's (parent page's) state in which it is called.
I've had to deal with a similar issue, and found another approach, using an object to reference the states between different functions, and in the same file.
import React, { useState } from "react";
let myState = {};
const GrandParent = () => {
const [name, setName] = useState("i'm Grand Parent");
myState.name=name;
myState.setName=setName;
return (
<>
<div>{name}</div>
<Parent />
</>
);
};
export default GrandParent;
const Parent = () => {
return (
<>
<button onClick={() => myState.setName("i'm from Parent")}>
from Parent
</button>
<Child />
</>
);
};
const Child = () => {
return (
<>
<button onClick={() => myState.setName("i'm from Child")}>
from Child
</button>
</>
);
};

Resources