Run useReadCypher inside useEffect - reactjs

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>
</>
);
}

Related

infinite scroll, react-intersection-observer, How to add new Array to the foundation Array? (i used spread... but didn't worked)

I'm making movie app (using react.js)
I want to show a list of new movies whenever user scrolls down.
but when i write these codes, it doesn't work.
I used react-intersection-observer and made the second useEffect for adding new list.
can you see what is the problem...?
**import { useInView } from "react-intersection-observer";**
import { useEffect, useRef, useState } from "react";
import Movie from "../components/Movie";
import HeaderComponent from "../components/HomeButton";
import GlobalStyle from "../GlobalStyle";
import { Route, useParams } from "react-router-dom";
import { LoadingStyle, ListContainer } from "../components/styles";
function Home() {
const [loading, setLoading] = useState(true);
const [movies, setMovies] = useState([]);
const [movieSearch, setMovieSearch] = useState("");
const [movieName, setMovieName] = useState("");
const [pageNumber, setPageNumber] = useState(1);
** const { ref, inView } = useInView({
threshold: 0,
});**
const param = useParams();
const getMovies = async () => {
const json = await (
await fetch(
`https://yts.mx/api/v2/list_movies.json?minimum_rating=1&page=${pageNumber}&query_term=${movieName}&sort_by=year`
)
).json();
setMovies(json.data.movies);
setLoading(false);
};
const onChange = event => {
setMovieSearch(event.target.value);
};
const onSubmit = event => {
event.preventDefault();
if (typeof param === Object) {
setMovieName(movieSearch);
getMovies();
} else {
Route(`/main`);
}
};
// When User Searching...
useEffect(() => {
getMovies();
}, [movieName]);
// When User Scroll, Keep Adding Movies at the bottom...
** useEffect(() => {
setPageNumber(prev => prev + 1);
setMovies(prev => {
return [...movies, ...prev];
});
getMovies();
}, [inView]);
**
return (
<>
<GlobalStyle />
<HeaderComponent
onSubmit={onSubmit}
onChange={onChange}
movieSearch={movieSearch}
/>
{loading ? (
<LoadingStyle>Loading...</LoadingStyle>
) : (
<>
<ListContainer>
{movies.map(item => {
return (
<Movie
key={item.title}
id={item.id}
title={item.title}
year={item.year}
medium_cover_image={item.medium_cover_image}
rating={item.rating}
runtime={item.runtime}
genres={item.genres}
summary={item.summary}
/>
);
})}
</ListContainer>
**{inView ? <>๐ŸŽญ</> : <>๐Ÿงถ</>}**
<div ref={ref} style={{ width: "100%", height: "20px" }}></div>
</>
)}
</>
);
}
export default Home;
and, when i debug this code, this errors comes out.
react_devtools_backend.js:4026 Warning: Encountered two children with the same key, `Headless Horseman`. Keys should be unique so that components maintain their identity across updates. Non-unique keys may cause children to be duplicated and/or omitted โ€” the behavior is unsupported and could change in a future version.
at div
at O (http://localhost:3000/static/js/bundle.js:47415:6)
at Home (http://localhost:3000/static/js/bundle.js:908:80)
at Routes (http://localhost:3000/static/js/bundle.js:41908:5)
at Router (http://localhost:3000/static/js/bundle.js:41841:15)
at BrowserRouter (http://localhost:3000/static/js/bundle.js:40650:5)
at App

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 fails on page refresh

I am an infant programmer and I am trying to fetch an api and style the results using React. My page works fine on the initial load and subsequent saves on VScode,but when I actually refresh the page from the browser I get the error thats posted on imageenter image description here:
Here is my code: App.js
```import React, { useEffect, useState } from 'react';
import './App.css';
import Students from './components/Students';
import styled from 'styled-components';
function App() {
const [studentInfo, setStudentInfo] = useState({});
const [searchResult, setSearchResult] = useState({});
const [searchTerm, setSearchTerm] = useState('');
useEffect(() => {
getStudents();
}, []);
useEffect(() => {
getStudents();
console.log('useEffect');
}, [searchTerm]);
const getStudents = async () => {
const url = 'https://api.hatchways.io/assessment/students';
console.log(url);
fetch(url)
.then((res) => res.json())
.then((data) => {
console.log(data);
searchTerm != ''
? setStudentInfo(filterStudents(data.students))
: setStudentInfo(data.students);
});
};
const filterStudents = (studentsArray) => {
return studentsArray.filter((info) => {
return (
info.firstName.toLowerCase().includes(searchTerm) ||
info.lastName.toLowerCase().includes(searchTerm)
);
});
};
console.log(searchTerm);
return (
<div className="App">
<Students
studentInfo={studentInfo}
setSearchTerm={setSearchTerm}
searchTerm={searchTerm}
/>
</div>
);
}
export default App;```
here is my component Students.js:
```import React, { useState } from 'react';
import styled from 'styled-components';
import GradeDetails from './GradeDetails';
const Students = ({ studentInfo, searchTerm, setSearchTerm }) => {
console.log(typeof studentInfo);
console.log(studentInfo[0]);
const [isCollapsed, setIsCollapsed] = useState(false);
const handleDetails = () => {
setIsCollapsed(!isCollapsed);
};
const average = (arr) => {
let sum = 0;
arr.map((num) => {
sum = sum + parseInt(num);
});
return sum / arr.length.toFixed(3);
};
console.log(isCollapsed);
return (
<Container>
<Input
type="text"
value={searchTerm}
placeholder="Search by name"
onChange={(e) => setSearchTerm(e.target.value.toLowerCase())}
/>
{studentInfo?.map((student) => (
<Wrapper key={student.id}>
<ImageContainer>
<Image src={student.pic}></Image>
</ImageContainer>
<ContentContainer>
<Name>
{student.firstName} {student.lastName}{' '}
</Name>
<Email>Email: {student.email}</Email>
<Company>Company: {student.company}</Company>
<Skills>Skill: {student.skill}</Skills>
<Average>Average:{average(student.grades)}%</Average>
</ContentContainer>
<ButtonContainer>
<Button onClick={handleDetails}>+</Button>
</ButtonContainer>
{isCollapsed && <GradeDetails studentInfo={studentInfo} />}
</Wrapper>
))}
</Container>
);
};```
Every time I have the error, I comment out the codes in Students.js starting from studentInfo.map until the and save and then uncomment it and save and everything works fine again.
I am hoping someone can help me make this work every time so that I don't have to sit at the edge of my seat all the time. Thank you and I apologize for the long question.
You are using an empty object as the initial state for studentInfo (the value passed to useState hook will be used as the default value - docs):
const [studentInfo, setStudentInfo] = useState({});
.map is only supported on Arrays. So this is failing when the component is rendering before the useEffect has completed and updated the value of studentInfo from an object, to an array. Try swapping your initial state to be an array instead:
const [studentInfo, setStudentInfo] = useState([]);

React Hide and Show a Component only using a Start button

I would like to hide two components in the Home component:
DisplayBox and GameBox.
When the user logs in the game starts automatically.
Instead, I'd like to only 'Show' the start button.
Then the user may press the Start button to start the game.
(will eventually have more levels to choose from in the start button component)
import React, { useState, useEffect } from "react";
import "./home.js";
import DisplayBox from '../components/displayBox';
import GameBox from '../components/gameBox/gameBox';
import randomWords from 'random-words'
import "./home.css";
const Home = () => {
const [numLetters, setNumLetters] = useState(5)
const [word, setWord] = useState("")
const [blank, setBlank ] = useState()
console.log("Blank", blank);
console.log("WORD", word)
const getARandomWord = () => {
setWord(randomWords(({ exactly: 1, maxLength: 4, formatter: (word) => word.toUpperCase() })))
}
useEffect(() => {
getARandomWord()
}, [])
function clickStart(){
// return { show: true};
// alert('You Start!');
}
return (
<>
<div>
<button onClick={clickStart} style={{width:"800px"}}>
START
</button>
</div>
<DisplayBox word={word} />
<GameBox numLetters={numLetters} setNumLetters={setNumLetters} word={word} setWord={setWord} getARandomWord={getARandomWord} />
</>
);
};
Home.propTypes = {};
export default Home;
create a new state to oversee whether the game is start or not then:-
Home.js:-
import React, { useState, useEffect } from "react";
import "./home.js";
import DisplayBox from '../components/displayBox';
import GameBox from '../components/gameBox/gameBox';
import randomWords from 'random-words'
import "./home.css";
const Home = () => {
const [numLetters, setNumLetters] = useState(5)
const [word, setWord] = useState("")
const [blank, setBlank ] = useState()
// state to track whether the game is start or not
const [isStart, setIsStart] = useState(false)
console.log("Blank", blank);
console.log("WORD", word)
const getARandomWord = () => {
setWord(randomWords(({ exactly: 1, maxLength: 4, formatter: (word) => word.toUpperCase() })))
}
useEffect(() => {
getARandomWord()
}, [])
// handle game start
function handleGameStart() {
if(isStart) {
// do something when game start
alert('Game starting!!!')
// reset the game again
// setIsStart(false)
} else {
console.log('Game is not starting!')
}
}
// function to oversee what happens after game start
useEffect(() => {
handleGameStart()
}, [isStart])
return (
<>
<div>
<button onClick={() => setIsStart(true)} style={{width:"800px"}}>
START
</button>
</div>
{/* this should only be available when game has started */}
{isStart && (
<>
<DisplayBox word={word} />
<GameBox numLetters={numLetters} setNumLetters={setNumLetters} word={word} setWord={setWord} getARandomWord={getARandomWord} />
</>
)}
</>
);
};
Home.propTypes = {};
export default Home;

lodash debounce in React functional component not working

I have a functional component built around the React Table component that uses the Apollo GraphQL client for server-side pagination and searching. I am trying to implement debouncing for the searching so that only one query is executed against the server once the user stops typing with that value. I have tried the lodash debounce and awesome debounce promise solutions but still a query gets executed against the server for every character typed in the search field.
Here is my component (with irrelevant info redacted):
import React, {useEffect, useState} from 'react';
import ReactTable from "react-table";
import _ from 'lodash';
import classnames from 'classnames';
import "react-table/react-table.css";
import PaginationComponent from "./PaginationComponent";
import LoadingComponent from "./LoadingComponent";
import {Button, Icon} from "../../elements";
import PropTypes from 'prop-types';
import Card from "../card/Card";
import './data-table.css';
import debounce from 'lodash/debounce';
function DataTable(props) {
const [searchText, setSearchText] = useState('');
const [showSearchBar, setShowSearchBar] = useState(false);
const handleFilterChange = (e) => {
let searchText = e.target.value;
setSearchText(searchText);
if (searchText) {
debounceLoadData({
columns: searchableColumns,
value: searchText
});
}
};
const loadData = (filter) => {
// grab one extra record to see if we need a 'next' button
const limit = pageSize + 1;
const offset = pageSize * page;
if (props.loadData) {
props.loadData({
variables: {
hideLoader: true,
opts: {
offset,
limit,
orderBy,
filter,
includeCnt: props.totalCnt > 0
}
},
updateQuery: (prev, {fetchMoreResult}) => {
if (!fetchMoreResult) return prev;
return Object.assign({}, prev, {
[props.propName]: [...fetchMoreResult[props.propName]]
});
}
}).catch(function (error) {
console.error(error);
})
}
};
const debounceLoadData = debounce((filter) => {
loadData(filter);
}, 1000);
return (
<div>
<Card style={{
border: props.noCardBorder ? 'none' : ''
}}>
{showSearchBar ? (
<span className="card-header-icon"><Icon className='magnify'/></span>
<input
autoFocus={true}
type="text"
className="form-control"
onChange={handleFilterChange}
value={searchText}
/>
<a href="javascript:void(0)"><Icon className='close' clickable
onClick={() => {
setShowSearchBar(false);
setSearchText('');
}}/></a>
) : (
<div>
{visibleData.length > 0 && (
<li className="icon-action"><a
href="javascript:void(0)"><Icon className='magnify' onClick= {() => {
setShowSearchBar(true);
setSearchText('');
}}/></a>
</li>
)}
</div>
)
)}
<Card.Body className='flush'>
<ReactTable
columns={columns}
data={visibleData}
/>
</Card.Body>
</Card>
</div>
);
}
export default DataTable
... and this is the outcome: link
debounceLoadData will be a new function for every render. You can use the useCallback hook to make sure that the same function is being persisted between renders and it will work as expected.
useCallback(debounce(loadData, 1000), []);
const { useState, useCallback } = React;
const { debounce } = _;
function App() {
const [filter, setFilter] = useState("");
const debounceLoadData = useCallback(debounce(console.log, 1000), []);
function handleFilterChange(event) {
const { value } = event.target;
setFilter(value);
debounceLoadData(value);
}
return <input value={filter} onChange={handleFilterChange} />;
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
<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>
To add onto Tholle's answer: if you want to make full use of hooks, you can use the useEffect hook to watch for changes in the filter and run the debouncedLoadData function when that happens:
const { useState, useCallback, useEffect } = React;
const { debounce } = _;
function App() {
const [filter, setFilter] = useState("");
const debounceLoadData = useCallback(debounce(fetchData, 1000), []);
useEffect(() => {
debounceLoadData(filter);
}, [filter]);
function fetchData(filter) {
console.log(filter);
}
return <input value={filter} onChange={event => setFilter(event.target.value)} />;
}
ReactDOM.render(<App />, document.getElementById("root"));
You must remember the debounced function between renders.
However, you should not use useCallback to remember a debounced (or throttled) function as suggested in other answers. useCallback is designed for inline functions!
Instead use useMemo to remember the debounced function between renders:
useMemo(() => debounce(loadData, 1000), []);
I hope this post will get you to the solution ,
You don't have to use external library for Debouncing you can create your own custom hook follow my steps
step(1):- Create the custom hook of Debouncing
import { useEffect ,useState} from 'react';
export const UseDebounce = (value,delay)=>{
const [debouncedValue,setDebouncedValue]= useState();
useEffect(()=>{
let timer = setTimeout(()=>setDebouncedValue(value),delay)
return ()=> clearTimeout(timer);
},[value])
return debouncedValue
}
step(2) :- Now create the file in which you want to add throttle
import React from 'react'
import { useEffect } from 'react';
import { useState } from 'react';
import {UseDebounce} from "./UseDebounce";
function Test() {
const [input, setInput] = useState("");
const debouncedValue = UseDebounce(input,1000);
const handleChange = (e)=>{
setInput(e.target.value)
}
useEffect(()=>{
UseDebounce&& console.log("UseDebounce",UseDebounce)
},[UseDebounce])
return (
<div>
<input type="text" onChange={handleChange} value={input}/>
{UseDebounce}
</div>
)
}
export default Test;
NOTE:- To test this file first create react app then embrace my files in it
Hope this solution worthwhile to you

Resources