how to pass the values between components without using props - reactjs

I want to pass the values from the counter component to Multiplier Component.I am using local storage for that.
I can not use props for this task niether useReducer, context etc.
Task is to on clicking the counter button from counter component it changes the value of multiplier component.
In App.js, the counter component works fine and store the value on localstorage but the multiplier component does not change its value on rendering in App.js as well as in the Counter component. I want to have change the value of Multiplier Component when i click on counter button.
Counter.js
import { useState, useEffect } from 'react'
import Multiplier from './components/Multiplier'
const Counter = () => {
const [counter, setCounter] = useState(1)
useEffect(() => {
localStorage.setItem('count', counter)
}, [])
const funcIncr = () => {
setCounter(counter => counter + 1);
localStorage.setItem('count', counter + 1)
showmult()
}
const funcDecr = () => {
setCounter(counter => counter - 1);
localStorage.setItem('count', counter - 1)
}
const showmult = () => {
return <Multiplier />
}
return (
<div className=''>
<button onClick={funcIncr}>+</button>
<div id='count'>{counter}</div>
<button onClick={funcDecr}>-</button>
<Multiplier />
</div>
)
}
export default Counter
Multiplier.js
import { useState, useEffect } from 'react'
const Multiplier = () => {
const [multiple, setMultiple] = useState(-5)
useEffect(() => {
let value = localStorage.getItem('count')
setMultiple(multiple=>multiple*value)
// multiplier()
}, [multiple])
// const multiplier = ()=>{
// // this.forceUpdate();
// let value = localStorage.getItem('count')
// setMultiple(multiple=>multiple*value)
// }
return (
<>
<div>Multiple: {multiple}</div>
</>
)
}
export default Multiplier
App.js
import Counter from './Counter'
import './App.css';
import Multiplier from './components/Multiplier';
function App() {
return (
<div className="App">
<div className='flex'>
<div>Counter</div>
<Counter/>
<Multiplier/>
</div>
</div>
);
}
export default App;

Counter.js
import React from 'react';
import { useState, useEffect } from 'react';
const Counter = () => {
const [counter, setCounter] = useState(1);
const funcIncr = () => {
setCounter((counter) => counter + 1);
localStorage.setItem('count', counter + 1);
};
const funcDecr = () => {
setCounter((counter) => counter - 1);
localStorage.setItem('count', counter - 1);
};
useEffect(() => {
let value = localStorage.getItem('count');
const count = localStorage.getItem('count');
const multiple = value * count;
document.getElementById('multipler').innerHTML = 'Multiple: ' + multiple;
}, [counter]);
return (
<div className="">
<button onClick={funcIncr}>+</button>
<div id="count">{counter}</div>
<button onClick={funcDecr}>-</button>
</div>
);
};
export default Counter;
Multiplier.js
import React from 'react';
const Multiplier = () => {
return <div id="multipler" />;
};
export default Multiplier;

A solution would be to use a Custom Event
window.dispatchEvent(
new CustomEvent('updateTitle', {
detail: {
firstName: values.firstName,
lastName: values.lastName,
},
}
)
You can add an event listener for the pages in which you want to listen to this event
useEffect(() => {
window.addEventListener("updateTitle", updateTitleEventHandler, false);
return () => {
window.removeEventListener("updateTitle", updateTitleEventHandler);
};
}, []);
const updateTitleEventHandler = useCallback((e) => {
//your logic
});

Related

Run useReadCypher inside useEffect

I'm writing React functional component that should be input for search on Neo4j.
I'm dependant on the useReadCypher and cannot change it's inner implementation.
I cannot write the useReadCypher inside the useEffect because it's break the rule of hooks.
import React, { useState, useEffect, useCallback } from 'react';
import { useReadCypher } from "use-neo4j";
export default function Search() {
const [count, setCount\] = useState(0);
const [runQuery, setRunQuery\] = useState(false);
const query = `MATCH (n) RETURN n LIMIT ${count}`;
const data = useReadCypher(query);
const handleClick = useCallback(() => {
setCount(count + 1);
setRunQuery(true);
}, [count]);
useEffect(() => {
if (runQuery) {
console.log('Data changed', data);
setRunQuery(false);
}
}, [data, runQuery]);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={handleClick}>
Click me
</button>
{JSON.stringify(data)}
</div>
);
}
I want to be able to click on the button to rerun the query using the useReadCypher.
What should be the approach to solving this issue?
Thank you.
It was the solution. Here is my final component.
import React, { useState, useEffect, useCallback } from 'react';
import { useReadCypher } from "use-neo4j";
import {Header} from "semantic-ui-react";
import {StyledDiv, StyledInput} from "./Style";
export default function Search() {
const [term, setTerm] = useState('');
const [runQuery, setRunQuery] = useState(false);
const query = `MATCH (n) RETURN n LIMIT ${term}`;
const {records, run} = useReadCypher(query);
const handleClick = useCallback(() => {
setRunQuery(true);
run();
}, [term]);
useEffect(() => {
if (runQuery) {
console.log('Data changed', records);
setRunQuery(false);
}
}, [records, runQuery]);
return (
<>
<Header as='H2' color='blue' textAlign='center' block>Search</Header>
<StyledDiv>
<StyledInput
value={term}
onChange={(e: any) => setTerm(e.target.value)}
/>
<button onClick={handleClick}>Search</button>
</StyledDiv>
<div>
{JSON.stringify(records)}
</div>
</>
);
}

Function components cannot have string refs. We recommend using useRef() instead

I'm creating a counter state using useState and useRef. However I'm getting this error
Here's my code
import { useEffect, useRef, useState} from 'react';
const App = () => {
const [clicks, setClick] = useState(0)
const myComponentDiv = useRef(null)
useEffect(() => {
if (myComponentDiv && myComponentDiv.current) {
myComponentDiv.current.addEventListener('click', clickHandler)
return () => {
myComponentDiv.current.removeEventListener('click', clickHandler)
}
}
}, [myComponentDiv]);
const clickHandler = () => {
setClick(clicks + 1)
}
return (
<div className="App">
<div className="my-component" ref="myComponentDiv">
<h2>My Component {clicks} clicks</h2>
</div>
</div>
);
}
export default App;
May i know where i did wrong?
Here:
ref="myComponentDiv"
should be:
ref={myComponentDiv}

useEffect called multiple times

I have the following reactjs code:
import React, { useState, useEffect } from 'react'
const Test = () => {
const [counter, setCounter] = useState(0)
useEffect(() => {
const data = localStorage.getItem('counter')
setCounter(parseInt(data, 10))
console.log('init', data)
}, [])
useEffect(() => {
localStorage.setItem('counter', counter)
console.log('changed', counter)
}, [counter])
const addCounter = () => {
setCounter((c) => c + 1)
console.log('added', counter)
}
return (
<div>
{counter}
<button onClick={addCounter}>+</button>
</div>
)
}
function App() {
return (
<div className='App'>
<Test />
</div>
)
}
The useEffect() hooks are being called multiple times. The states are persisted in localStorage. But upon page refresh, the states reset to default values:
init 4 <--- counter was incremented to 4 at previous session
changed 0 <-- counter is reset to 0??
init 0 <-- this should not appear
changed 0
What am I doing wrong?
You can use this boilerplate to avoid repeated renders while in <StrictMode>
function App() {
const [success, setSuccess] = useState(false);
const isMounted = useRef(false);
useEffect(() => {
console.log('started');
if (isMounted.current) {
console.log('mounted');
} else {
console.log('mounting');
isMounted.current = true;
}
}, [success]);
const handleClick = (e) => {
setSuccess(!success);
}
return (
<div className="App">
<header className="App-header">
<button className="button" onClick={handleClick}>Action</button>
</header>
</div>
);
}

Call a function from a class in a different file - React

I'm basically trying to call a function (getValue) from a class (Time) in a different file, but there is some issues.
Here is the code for the two files:
Time.js
export default class Time extends Component {
constructor(props) {
super(props);
this.state = {
input: '',
input2: '',
checked: false
}
this.getValue = this.getValue.bind(this);
}
hrChange = e => {
this.setState({input: e.target.value}, function () {this.getValue()})
}
minChange = e => {
this.setState({input2: e.target.value}, function () {this.getValue()})
}
amPm = () => {
this.setState({checked: !this.state.checked}, function () {this.getValue()})
}
getValue = () => {
const list = [
this.state.input,
this.state.input2,
this.state.checked
]
return (list)
}
render() {
return(
<text>some stuff</text>
)
}
}
NewStorage.js
function NewStorage() {
const time = () => {
var obj = new Time();
var list = obj.getValue()
const
hrInput = list[0],
minInput = list[1],
pm = list[2]
return(
console.log(hrInput, minInput, pm, list)
)
return(
time()
)
}
export default NewLocalStorage;
The main issue isn't that I can't call the function, it is that when I call the function, the values of input, input2, and checked are all the original value ('', '', false), not the updated versions (ex: '11', '30', true).
I'm not sure on how to solve this issue.
Your inclusion of the react-hooks tag suggest your hunch that hooks are applicable to solving your problem. I would agree -
const { useState, useEffect } = React
function Time ({ hour, minute, onChange }) {
const [h,setHour] = useState(hour)
const [m,setMinute] = useState(minute)
useEffect(_ => onChange({ hour: h, minute: m }), [h, m])
return <div>
<input value={h} onChange={event => setHour(event.target.value)} />
<input value={m} onChange={event => setMinute(event.target.value)} />
</div>
}
ReactDOM.render(<Time onChange={console.log} />, document.querySelector("main"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<main></main>
In a more sophisticated example, we can use the Time component's onChange callback to update nested state in a parent component, MyForm -
const { useState, useEffect, useCallback } = React
function Time ({ hour = 0, minute = 0, onChange }) {
const [h,setHour] = useState(hour)
const [m,setMinute] = useState(minute)
useEffect(_ => onChange({ hour: h, minute: m }), [h, m, onChange])
return <div>
<input value={h} onChange={event => setHour(event.target.value)} />
<input value={m} onChange={event => setMinute(event.target.value)} />
</div>
}
function MyForm () {
const [data, setData] = useState({ time: { hour: 5, minute: 30 }, foo: "bar" })
const onTimeChange = useCallback(t => setData({ ...data, time: t }), [])
return <form>
<Time hour={data.time.hour} minute={data.time.minute} onChange={onTimeChange} />
<pre>{JSON.stringify(data, null, 2)}</pre>
</form>
}
ReactDOM.render(<MyForm />, document.querySelector("main"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<main></main>
Instead of trying to create a class and call the function in another file, why not use React functional components and hooks?
Try something like this:
const Clock = () => {
const [hour, setHour] = useState();
const [min, setMin] = useState();
const [am, setAm] = useState(true);
useEffect(() => {
// Get your clock to work in here...
}, [hour, min, am]);
return (
<div>
{//This will post your clock here, and if you need the values, you
can set/use them individually as needed.}
{hour}:{min} {am ? 'am' : 'pm'}
{//The ternary statement will modify this portion for you in code.}
</div>
);
}
If you want to use the values globally, you may want to try using the React hook useContext(). This will allow you to access those specific values anywhere you want, but requires a bit more setup.
Context, if you don't know will turn your react app into Redux, without using Redux. Below is an example of what you need to do.
import { createContext } from "react";
export const QuizContext = createContext();
then you add the context to your App.js:
import { useState } from 'react';
import './App.css';
import MainMenu from './Components/MainMenu';
import Quiz from './Components/Quiz';
import EndScreen from './Components/EndScreen';
import { QuizContext } from './Helpers/Context';
function App() {
const [gameState, setGameState] = useState('Menu');
const [score, setScore] = useState(0);
return (
<div className="App">
<h1>Quiz App</h1>
<QuizContext.Provider value={{gameState, setGameState, score, setScore}}>
{gameState === 'Menu' && <MainMenu/>}
{gameState === 'Quiz' && <Quiz/>}
{gameState === 'EndScreen' && <EndScreen/>}
</QuizContext.Provider>
</div>
);
}
Then you can access the context from individual components as long as they are children of App.
Example:
import React, { useContext, useState } from 'react';
import { QuizContext } from '../Helpers/Context';
import {Questions} from '../Helpers/QuestionBank'
const Quiz = () => {
const [currentQuestion, setCurrentQuestion] = useState(0)
const [optionChosen, setOptionChosen] = useState('');
const {setGameState, score, setScore} = useContext(QuizContext);
const nextQuestion = () => {
Questions[currentQuestion].answer === optionChosen ? setScore(score + 1) : console.log(score);
setCurrentQuestion(currentQuestion + 1);
}
const finishQuiz = () => {
Questions[currentQuestion].answer === optionChosen ? setScore(score + 1) : console.log(score);
setGameState('EndScreen');
}
return (
<div className="Quiz">
<h1>{Questions[currentQuestion].prompt}</h1>
<div className="options">
<button onClick={() => setOptionChosen('optionA')}>{Questions[currentQuestion].optionA}</button>
<button onClick={() => setOptionChosen('optionB')}>{Questions[currentQuestion].optionB}</button>
<button onClick={() => setOptionChosen('optionC')}>{Questions[currentQuestion].optionC}</button>
<button onClick={() => setOptionChosen('optionD')}>{Questions[currentQuestion].optionD}</button>
</div>
{currentQuestion === Questions.length -1 ? <button onClick={finishQuiz}>Finish Quiz</button> : <button onClick={nextQuestion}>Next Question</button>}
</div>
)
}
export default Quiz
I learned this method from a Tutorial from PedroTech on YouTube. I followed along to create this. I wanted to make sure I didn't take credit for his work.

Timer with react hooks doesn't update after re-rendering

I'm creating a timer with React hooks. It's a simple component that is used in a quiz. Each question has a defined duration so the timer should start at this duration and start decreasing one second at a time. My problem is that the component does what is supposed to do, but when I go to the next question the state doesn't initialize to the duration passed in the props but continues with the counter...
const Timer = ({ duration }) => {
const [counter, setCounter] = useState(duration);
const timer = useRef();
debugger;
console.log("counter: " + counter);
const setTimer = () => {
if (counter <= duration) {
setCounter(counter - 1);
}
};
// Similar to componentDidMount and componentDidUpdate:
useEffect(() => {
timer.current = setTimeout(() => setTimer(), 1000);
return () => {
if (timer.current) {
console.log("ClearInterval in Timer");
clearTimeout(timer.current);
}
};
}, [counter]);
return (
<div>
<p>time left {counter} seconds</p>
</div>
);
};
export default Timer;
I'm rendering the Timer component from the Card component:
import React from "react";
import QuizButton from "../QuizButton/QuizButton";
import Timer from "../Timer/Timer";
import "./styles.css";
const QuizQuestion = ({ question, responses, checkAnswerFn, duration }) => (
<article className='card'>
<header>
<Timer duration={duration} />
</header>
<div>
<p>{question}</p>
</div>
<footer>
{responses.map((response, i) => {
return (
<QuizButton key={i} onClick={checkAnswerFn(response)}>
{response}
</QuizButton>
);
})}
</footer>
</article>
);
export default QuizQuestion;
Does anyone know why the state is not initialized to duration after the question re-renders?
Look a bit complex to drive you how and why your code is not working as expected, but here is a working example just did it today, hope it helps to point you in the right direction:
const [counter, setCounter] = useState(15000);
...
useEffect(() => {
const myInterval = () => {
if (counter > 1000) {
setCounter(state => state - 1000)
} else if (counter !== 0){
setCounter(0);
clearInterval(interval);
}
}
const interval = setInterval(myInterval, 1000);
return () => {
clearInterval(interval)
}
}, [counter]);
...
console.log(counter); // 15000, 14000, 13000, ...
The condition in the setTimer function is not allowing the counter to initialize. Let your function decrement the counter until and unless the counter value is greater than 0 otherwise initialize it with the duration. Replace your function with the following, it will help you initialize the timer back to the duration.
const setTimer = () => {
setCounter(counter > 0 ? counter - 1 : duration);
};
UPDATED 2 - Added Question Array and Question based timer data
UPDATED 1 - I THINK IT WORKS NOW AS YOUR WISH TO TO BE LIKE
Summary - I have lifted some states up.
So the edited answer is: Since we need to resend new prop data to the Child Timer component while the trigger was based on the child component which I have demonstrated using a click from the child Timer component, we passed a function from the parent Question component to the child component which would be called on an onClick listener via the prop. Therefore the function updates the parent's (Question.js) state re-rendering the child (Timer.js) and passing new prop values whichever we have set as new. Rather than relaying on timer based change we also changed to data based trigger like the Question Data changed to invoke the useEffect.
I hope the explanation works and and solved your use case.
..
//Question.js
import React, { useState, useEffect } from "react";
import Timer from "./Timer";
import "./styles.css";
const Question = () => {
const questionPool = [
{ number: 1, text: "lorem", timer: 6 },
{ number: 2, text: "lorem", timer: 10 },
{ number: 3, text: "lorem", timer: 20 },
{ number: 4, text: "lorem", timer: 3 },
{ number: 5, text: "lorem", timer: 12 }
];
const [countDown, setCountDown] = useState(questionPool[0].timer);
const [currentQuestion, setCurrentQuestion] = useState(
questionPool[0].number
);
const resetCount = (newQuestion) => {
newQuestion < questionPool.length + 1
? setCurrentQuestion(newQuestion)
: setCurrentQuestion(1);
};
useEffect(() => {
if (questionPool[currentQuestion]) {
setCountDown(questionPool[currentQuestion].timer);
} else {
setCountDown(questionPool[0].timer);
}
}, [currentQuestion]);
return (
<>
<Timer
duration={countDown}
resetCountDown={resetCount}
questionNumber={currentQuestion}
/>
</>
);
};
export default Question;
....
//Timer.js
import React, { useEffect, useState } from "react";
import "./styles.css";
const Timer = ({ duration, resetCountDown, questionNumber}) => {
const [counter, setCounter] = useState(duration);
let interval = null;
useEffect(() => {
const myInterval = () => {
if (counter > 1) {
setCounter((state) => state - 1);
} else if (counter !== 0) {
setCounter(0);
clearInterval(interval);
}
};
interval = setInterval(myInterval, 1000);
return () => {
clearInterval(interval);
};
}, [counter]);
useEffect(() => {
if (!interval) {
setCounter(duration);
}
}, [questionNumber]);
return (
<div>
<p>
time left {counter} seconds For Question: {questionNumber}
</p>
<button type="button" onClick={() => resetCountDown(questionNumber + 1)}>
next
</button>
</div>
);
};
export default Timer;
CodeSandbox Extending from #Adolfo Onrubia to match your use case
Check if this helps.
********* DEPRECATED BELOW *******************
If you still want to dynamically add timer component, I think you need to push in a whole react component in the prop and have the component which is pushing a new component re-render so the child will render itself and a new question with a new component can appear.
In short,
Your QuestionsArray[{OBJECT WITH QUESTION DETAILS}] is in your main app.
Your have a useState like :
const [currentQuestion, setCurrentQuestion] =
useState(QuestionArray[0]);
And whenever a next button is pushed. You do
setCurrentQeustion(QuestionArray[NewIndex]);
Thus your master component App Components' State re-renders on useEffect of currentQuestion and passes that to the Timer.js/child component causing it to rerender with new Timeout duration.
It may also be worthwhile to check on props.children so rather than just sending as a regular prop you pass as template like.
<Template>
{currentQuestion}
</Template>
Thanks to Fauzul and Adolfo, I finally solved my issue.
I pass the id of the question to the props of the timer, and using the useEffect hook, everytime the id changes the counter gets updated to the duration...
QuizQuestion.js
import React from "react";
import PropTypes from "prop-types";
import QuizButton from "../QuizButton/QuizButton";
import Timer from "../Timer/Timer";
import "./styles.css";
const QuizQuestion = ({ question, responses, checkAnswerFn, duration, id }) => (
<article className='card'>
<header>
<Timer duration={duration} id={id} />
</header>
<div>
<p>{question}</p>
</div>
<footer>
{responses.map((response, i) => {
return (
<QuizButton key={i} onClick={checkAnswerFn(response)}>
{response}
</QuizButton>
);
})}
</footer>
</article>
);
QuizQuestion.propTypes = {
question: PropTypes.string.isRequired,
responses: PropTypes.array.isRequired,
checkAnswerFn: PropTypes.func.isRequired,
};
export default QuizQuestion;
Timer.js
import React, { useState, useEffect, useRef } from "react";
import PropTypes from "prop-types";
const Timer = ({ duration, id }) => {
const [counter, setCounter] = useState(duration);
const timer = useRef();
const setTimer = () => {
if (counter > 0) {
setCounter(counter - 1);
}
};
// Similar to componentDidMount and componentDidUpdate:
useEffect(() => {
timer.current = setTimeout(() => setTimer(), 1000);
return () => {
if (timer.current) {
clearTimeout(timer.current);
}
};
}, [counter]);
useEffect(() => {
setCounter(duration);
}, [id]);
return (
<div>
<p>time left {counter} seconds</p>
</div>
);
};
Timer.propTypes = {
duration: PropTypes.number.isRequired,
id: PropTypes.number.isRequired,
};
export default Timer;

Resources