Not able to convert react custom hook to be a context - javascript

I have a custom hook(useData) that takes query as an argument and then returns data and runtime(time to fetch the data from the API). But I need access to the runtime to my Editor component when I click on the run button. Right now what is happening is when I click on run button(inside Editor.js), it sets the query to the App component using the setter function and then it passes that query to the Table component and then calls the custom hook using that query and then table make use of that data. but I want the runtime in the Editor component, not in the Table component. I know I can call useData hook in the Editor component but my editor component gets rerender every time when we write on the editor, so It calls the useData() hook on each change.
If I create a context using this hook then I can able to access the runtime and data wherever I want.
Anyone, please help me how to convert that to context!
App.js code
import React, { useState } from "react";
import "./assets/output.css";
import Footer from "./components/layouts/Footer";
import Navbar from "./components/layouts/Navbar";
import Sidebar from "./components/layouts/Sidebar";
import TableSection from "./components/table/TableSection";
import Editor from "./components/editor/Editor";
const App = () => {
const [query, setQuery] = useState("");
const [value, setValue] = useState("select * from customers");
return (
<>
<div className="grid grid-cols-layout-desktop grid-rows-layout-desktop bg-gray-600 h-screen">
<Navbar />
<Sidebar setQuery={setQuery} setValue={setValue} />
<Editor setQuery={setQuery} value={value} setValue={setValue} />
{query ? <TableSection query={query} /> : null}
<Footer />
</div>
</>
);
};
export default App;
Editor.js
import React from "react";
import AceEditor from "react-ace";
import "ace-builds/src-min-noconflict/ext-language_tools";
import "ace-builds/src-min-noconflict/mode-mysql";
import "ace-builds/src-noconflict/theme-github";
import useData from "../../hooks/useData";
const Editor = ({ setQuery, value, setValue }) => {
const { runtime } = useData();
const onChange = (newValue) => {
setValue(newValue);
};
const onSubmit = () => {
var Z = value.toLowerCase().slice(value.indexOf("from") + "from".length);
setQuery(Z.split(" ")[1]);
};
return (
<div className="col-start-2 col-end-3 row-start-2 row-end-3 m-6">
<AceEditor
aria-label="query editor input"
mode="mysql"
theme="github"
name={Math.floor(Math.random() * 100000).toString()}
fontSize={16}
minLines={15}
maxLines={10}
width="100%"
showPrintMargin={false}
showGutter
placeholder="Write your Query here..."
editorProps={{ $blockScrolling: true }}
setOptions={{
enableBasicAutocompletion: true,
enableLiveAutocompletion: true,
enableSnippets: true,
}}
value={value}
onChange={onChange}
showLineNumbers
/>
<div className="">
<button
className="bg-white text-gray-800 rounded-md font-semibold px-4 py-2 my-4"
onClick={onSubmit}
>
<i className="fas fa-play"></i> Run SQL
</button>
</div>
</div>
);
};
export default Editor;
Hook code:
import { useEffect, useState } from "react";
import alasql from "alasql";
import toast from "react-hot-toast";
import TABLE_NAMES from "../utils/tableNames";
const getURL = (name) =>
`https://raw.githubusercontent.com/graphql-compose/graphql-compose-examples/master/examples/northwind/data/csv/${name}.csv`;
const useData = (tableName) => {
const [data, setData] = useState([]);
const [error, setError] = useState(false);
const [runtime, setRuntime] = useState("");
const convertToJson = (data) => {
alasql
.promise("SELECT * FROM CSV(?, {headers: false, separator:','})", [data])
.then((data) => {
setData(data);
toast.success("Query run successfully");
})
.catch((e) => {
toast.error(e.message);
});
};
const fetchData = (tableName) => {
setData([]);
const name = TABLE_NAMES.find((name) => name === tableName);
if (name) {
setError(false);
fetch(getURL(tableName))
.then((res) => res.text())
.then((data) => convertToJson(data));
} else {
setError(true);
toast.error("Please enter a valid query");
}
};
useEffect(() => {
let t0 = performance.now(); //start time
fetchData(tableName);
let t1 = performance.now(); //end time
setRuntime(t1 - t0);
console.log(
"Time taken to execute add function:" + (t1 - t0) + " milliseconds"
);
}, [tableName]);
return { data, runtime, error };
};
export default useData;

If you want to create a context and use it wherever you want, you can create a context, and add the state in this component and pass it to the value prop in the Provider component.
See the sample code.
import React, { createContext, useState } from "react";
export const UserContext = createContext({});
export interface User {
uid: string;
email: string;
}
export const UserProvider = ({ children }: any) => {
const [user, setUser] = useState<User>();
// you can defined more hooks at here
return (
// Pass the data to the value prop for sharing data
<UserContext.Provider value={{ user, setUser }}>
{children}
</UserContext.Provider>
);
};
Then wrap components with the provider function like this
<UserProvider>
<MyComponment1>
</MyComponment1>
<MyComponment2>
</MyComponment2>
<MyComponment3>
</MyComponment3>
</UserProvider>
At This time, Whatever Component in the UserProvider can access the context right now and you can use useContext hook to access the data that you pass in the value props
export const MyComponment1 = () => {
const { user, setUser } = useContext<any>(UserContext);
...
}

Related

Trying to display one element from an Array -ReactJs

I am trying to make a flashcard web app for language learning and/or rote learning. I have managed to show the first element of the array which contains the data that I'm fetching from the backend but I can't switch from the first element to the subsequent elements.
Here is my code in React:
// Decklist component that displays the flashcard
import { React, useEffect, useState, useContext } from "react";
import Card from "./Card";
import cardContext from "../store/cardContext";
const axios = require("axios");
export default function Decklist() {
//State for data fetched from db
const [data, setData] = useState([]);
//State for array element to be displayed from the "data" state
const [position, setPosition] = useState(0);
//function to change the array element to be displayed after user reads card
const setVisibility = () => {
setPosition(position++);
};
//function to change the difficulty of a card
const difficultyHandler = (difficulty, id) => {
console.log(difficulty);
setData(
data.map((ele) => {
if (ele.ID === id) {
return { ...ele, type: difficulty };
}
return ele;
})
);
};
//useEffect for fetching data from db
useEffect(() => {
axios
.get("/api/cards")
.then((res) => {
if (res.data) {
console.log(res.data);
setData(res.data.sort(() => (Math.random() > 0.5 ? 1 : -1)));
}
})
.catch((err) => {
console.log(err);
});
}, []);
return (
<cardContext.Provider
value={{ cardData: data, setDifficulty: difficultyHandler }}
>
{data.length && (
<Card
position={position}
// dataIndex={index}
visible={setVisibility}
id={data[position].ID}
front={data[position].Front}
back={data[position].Back}
/>
)}
</cardContext.Provider>
);
}
//Card component
import { React, useState, useEffect } from "react";
import Options from "./Options";
export default function Card(props) {
//State for showing or hiding the answer
const [reverse, setReverse] = useState(false);
const [display, setDisplay] = useState(true);
//function for showing the answer
const reversalHandler = () => {
setReverse(true);
};
return (
<div>
{reverse ? (
<div className="card">
{props.front} {props.back}
<button
onClick={() => {
props.visible();
}}
>
Next Card
</button>
</div>
) : (
<div className="card">{props.front}</div>
)}
<Options
visible={props.visible}
reverse={reversalHandler}
id={props.id}
/>
</div>
);
}
//Options Component
import { React, useContext, useState } from "react";
import cardContext from "../store/cardContext";
export default function Options(props) {
const ctx = useContext(cardContext);
const [display, setDisplay] = useState(true);
return (
<>
<div className={display ? "" : "inactive"}>
<button
onClick={() => {
setDisplay(false);
props.reverse();
ctx.setDifficulty("easy", props.id);
}}
>
Easy
</button>
<button
onClick={() => {
setDisplay(false);
props.reverse();
ctx.setDifficulty("medium", props.id);
}}
>
Medium
</button>
<button
onClick={() => {
setDisplay(false);
props.reverse();
ctx.setDifficulty("hard", props.id);
}}
>
Hard
</button>
</div>
</>
);
}
The setVisibility function in the Decklist component is working fine and setting the position state properly. However, I don't know how to re-render the Card component so that it acts on the position state that has changed.
One way to force a re-render of a component is to set its state to itself
onClick={() => {
props.visible();
setReverse(reverse);
}}
However this probably isn't your issue as components will automatically re-render when their state changes or a parent re-renders. This means that for some reason the Card component isn't actually changing the parent component.

React tabulator ref becomes null on rerendering

I am using Tabulator with React using the react-tabulator module.
I have used 'ref' for the table component and call actions like downloading data or whatever it may be.
import React, { Suspense, useEffect, useReducer, useRef } from 'react';
import PropTypes from 'prop-types';
import "react-tabulator/lib/styles.css"; // default theme
import "react-tabulator/css/tabulator_midnight.min.css";
import {Button } from 'react-bootstrap';
import { ReactTabulator, reactFormatter } from "react-tabulator";
import { reducer } from '../common/reducer';
import ChangeStaffCompetency from './ChangeStaffCompetency';
import * as jsPDF from 'jspdf';
import 'jspdf-autotable';
window.jspdf = jsPDF;
const luxon = require('luxon');
window.DateTime = luxon.DateTime;
// Initial states of StaffCompetency
const initialState = {
changeStaffCompetencyShow: false,
staffID: "",
workflowName: "",
competentTasks: "",
competencyEditorRow: null,
};
const StaffCompetency = (props) => {
// State Handling
const [state, dispatch] = useReducer(reducer, initialState);
// Reference for the tabulator
let staffCompetencyTableRef = useRef(null);
// Action to download workloads data as 'JSON'
const downloadAsJSON = () => {
staffCompetencyTableRef.current.download("json", "RTP_Staff_Competencies.json");
}
/***
* ALL OTHER CODE
*/
return (
<>
<h3 className="text-center"> Staff Competency </h3>
<div>
<Button variant="dark" onClick={() => downloadAsJSON()}>Download JSON</Button>{' '}
</div>
<div style={{clear: 'both'}}></div>
<br></br>
<ReactTabulator
onRef={(r) => (staffCompetencyTableRef = r)}
columns={staffCompetencyTableCoumns}
options={staffCompetencyTableOptions}
/>
<ChangeStaffCompetency
show={state.changeStaffCompetencyShow}
onHide={() => dispatch({ type: "changeStaffCompetencyShow", value: false })}
staffID= {state.staffID}
workflowName= {state.workflowName}
competentTasks= {state.competentTasks}
api={props.api}
parentCallback = {handleCallback}
/>
</>
);
}
StaffCompetency.propTypes = {
api: PropTypes.object.isRequired
};
export default StaffCompetency;
ChangeStaffCompetency is a react-bootstrap modal component which is used as a custom editor to edit the contents of the cell.
staffCompetencyTableRef works fine on the first render but it becomes null on rerendering; for instance when I open and close the ChangeStaffCompetency modal.
How would I resolve this?
Thanks
I solved the issue by changing the type of my useRef variable (staffCompetencyTableRef) to const and used the property of const variables to do my work.
const StaffCompetency = (props) => {
// State Handling
const [state, dispatch] = useReducer(reducer, initialState);
// Reference for the tabulator
const staffCompetencyTableRef = useRef(null);
// Action to download workloads data as 'JSON'
const downloadAsJSON = () => {
staffCompetencyTableRef.current.download("json", "RTP_Staff_Competencies.json");
}
/***
* ALL OTHER CODE
*/
return (
<>
<h3 className="text-center"> Staff Competency </h3>
<div>
<Button variant="dark" onClick={() => downloadAsJSON()}>Download JSON</Button>{' '}
</div>
<div style={{clear: 'both'}}></div>
<br></br>
<ReactTabulator
onRef={(r) => (staffCompetencyTableRef.current = r.current)}
columns={staffCompetencyTableCoumns}
options={staffCompetencyTableOptions}
/>
<ChangeStaffCompetency
show={state.changeStaffCompetencyShow}
onHide={() => dispatch({ type: "changeStaffCompetencyShow", value: false })}
staffID= {state.staffID}
workflowName= {state.workflowName}
competentTasks= {state.competentTasks}
api={props.api}
parentCallback = {handleCallback}
/>
</>
);
}
It kind of feels like a trick. If anyone knows a better approach, please do comment.
Thanks

React Query only display on tab refresh

I find myself in a bit of a pickle and can't seem to find an answer on Google.
I'm trying to use the React query library with TSX and display the returning data in a simple list. However it seems that the fetchng and displaying is done only by leaving the tab and coming back to it.
Here's the component
import React, { ChangeEvent, useState, ReactElement } from "react";
import { useQuery, UseQueryResult } from "react-query";
import axios from "axios";
import { API_URL } from "../../settings";
import SearchBar from "../../components/search-bar";
const Employees = (): ReactElement => {
type Employee = Record<string, any>;
const [name, setName] = useState("");
function getValue(eventData: ChangeEvent<HTMLInputElement>) {
console.log(name, "I'm the direct input");
const e = eventData.target.value;
setName(e);
getEmployeesList(name);
}
async function getEmployeesList(name: string) {
const { data } = await axios.get(
API_URL + "employees?q[firstname_or_lastname_cont]=" + name
);
console.log(data);
return data;
}
const {
data,
error,
isError,
isLoading,
}: UseQueryResult<Employee[], Error> = useQuery("employees", () =>
getEmployeesList(name)
);
if (isLoading) {
return <div>Loading...</div>;
}
if (isError) {
return (
<div>
`Error!
{error?.message}`
</div>
);
}
if (data) {
console.log(data, "I'm the query data");
}
return (
<div className="findEmployees">
<SearchBar
placeholder=""
value={name}
onValueChange={(event: ChangeEvent<HTMLInputElement>) =>
getValue(event)
}
/>
<div className="listContainer">
<h1>Employees</h1>
{data?.map((employee, index: number) => (
<li key={employee.id}>
{employee.firstname} {employee.lastname}
<p>{employee.role}</p>
</li>
))}
</div>
</div>
);
};
export default Employees;
Here's the child component
import React, {
ChangeEventHandler,
MouseEvent,
ReactElement,
ReactEventHandler,
} from "react";
import { SearchBarContainer, SearchBarInput } from "./styled-components";
import Icon from "../icon";
interface Props {
placeholder: string;
value: string;
onValueChange: ChangeEventHandler<HTMLInputElement>;
}
const SearchBar = ({
onValueChange,
placeholder,
value,
}: Props): ReactElement => (
<SearchBarContainer>
<SearchBarInput
onChange={onValueChange}
placeholder={placeholder}
value={value}
/>
<Icon color="grey700" name="search" />
</SearchBarContainer>
);
export default SearchBar;
So far I haven't found the problem. I tried a custom hook to get and set the data but that obviously didn't change the problem. If anyone has an idea I'll be thankful.
Have a great day
function getValue(eventData: ChangeEvent<HTMLInputElement>) {
const e = eventData.target.value;
setName(e);
console.log(name); // still you should receive previous value
getEmployeesList(e);
}
setState isn't synchronous. It only updates the state value at end of the function call.
thanks for your inputs. I actually solved the problem with a custom hooks
import React, { ChangeEvent, useState, ReactElement } from "react";
import { useQuery, UseQueryResult } from "react-query";
import axios from "axios";
import { API_URL } from "../../settings";
import SearchBar from "../../components/search-bar";
const Employees = (): ReactElement => {
type Employee = Record<string, any>;
const [name, setName] = useState<string>("");
const [eData, setEData] = useState<Employee[]>([]);
function getValue(eventData: ChangeEvent<HTMLInputElement>) {
setName(eventData.target.value);
getEmployeesList(eventData.target.value);
}
async function getEmployeesList(name: string) {
const { data } = await axios.get(
API_URL + "employees?q[firstname_or_lastname_cont]=" + name
);
setEData(data);
return data;
}
const {
data,
error,
isError,
isLoading,
}: UseQueryResult<Employee[], Error> = useQuery("employees", () =>
getEmployeesList(name)
);
if (isLoading) {
return <div>Loading...</div>;
}
if (isError) {
return (
<div>
`Error!
{error?.message}`
</div>
);
}
return (
<div className="findEmployees">
<SearchBar
placeholder=""
value={name}
onValueChange={(event: ChangeEvent<HTMLInputElement>) =>
getValue(event)
}
/>
<div className="listContainer">
<h1>Employees</h1>
{eData?.map((employee, index: number) => (
<li key={employee.id}>
{employee.firstname} {employee.lastname}
<p>{employee.role}</p>
</li>
))}
</div>
</div>
);
};
export default Employees;
As you can see I changed "data" by the hook value "eData". I noticed that my axios query was updated in real time so I took this entry and stocked it in a custom hook which I then mapped on my JSX. Got a real time fetch that way. Furthermore I updated the
function getValue(eventData: ChangeEvent<HTMLInputElement>) {
setName(eventData.target.value);
getEmployeesList(eventData.target.value);
}
part which was requesting with one letter fewer in the first version.

I am using useEffect to hit api and return some response to display it.why this error is happening

I am using useEffect to hit an api and display some data from the response.It works well in console but when i try to display the data in a component it throws an error.I am checking for the loading state though.I am showing the data after a i get a response then where does this null coming from
App.js file:
import { useState, useEffect } from 'react';
import Details from './components/Details/Details';
import Header from './components/Header/Header';
import GlobalStyle from './globalStyles';
const API_KEY = 'Private';
// const URL = `https://geo.ipify.org/api/v1?apiKey=${API_KEY}&ipAddress=${ip}`;
function App() {
const [ip, setIp] = useState('8.8.8.8');
const [response, setResponse] = useState(null);
const [error, setError] = useState(null);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
const fetchData = async () => {
setIsLoading(true);
try {
const res = await fetch(
`https://geo.ipify.org/api/v1?apiKey=${API_KEY}&ipAddress=${ip}`
);
const json = await res.json();
setResponse(json);
setIsLoading(false);
} catch (error) {
setError(error);
}
};
fetchData();
// return { response, error, isLoading };
}, [ip]);
return (
<>
<GlobalStyle />
<Header getIp={(q) => setIp(q)} />
<Details isLoading={isLoading} res={response} error={error} />
</>
);
}
export default App;
Header.js file:
import { useState } from 'react';
import { FaArrowRight } from 'react-icons/fa';
import React from 'react';
import { Form, FormInput, Head, HeadLine, Button } from './Header.elements';
// import { useFetch } from '../../useFetch';
const Header = ({ getIp }) => {
const [input, setInput] = useState('');
const onChange = (q) => {
setInput(q);
getIp(q);
};
return (
<>
{/* styled components */}
<Head>
<HeadLine>IP Address Tracker</HeadLine>
<Form
onSubmit={(e) => {
e.preventDefault();
onChange(input);
setInput('');
}}
>
<FormInput
value={input}
onChange={(e) => {
setInput(e.target.value);
}}
placeholder='Search for any IP address or Domain'
/>
<Button type='submit'>
<FaArrowRight />
</Button>
</Form>
</Head>
</>
);
};
export default Header;
Details.js file:
import React from 'react';
import { Box, Location } from './Details.elements';
const Details = ({ res, error, isLoading }) => {
console.log(res);
return isLoading ? (
<div>loading...</div>
) : (
<>
<Box>
<Location>{res.location.city}</Location>
</Box>
</>
);
};
export default Details;
the error it shows:
That happens because on the first render, Details component will receive isLoading=false and res=null, so it will try to render the box so it's throwing the error.
You can initialize isLoading as true.
const [isLoading, setIsLoading] = useState(true);
Or render the Location if res has some value.
<Box>
{res && <Location>{res.location.city}</Location>}
</Box>
According to React documentation :
https://reactjs.org/docs/hooks-reference.html
By default, effects run after every completed render, but you can
choose to fire them only when certain values have changed.
So your component is rendering at least once with isLoading as false before even the API call starts.
You have two choices here:
Set isLoading initial value to true
Add optional chaining res?.location.city
https://codesandbox.io/s/stackoverflow-67755606-uuhqk

Can I fix the issue when I call an api, it called two times with reactjs?

I used redux-saga and I want when I click on my button, the api will be fetching,
My code is:
// #flow
import React, { useEffect, useState, useRef } from 'react';
import { connect } from 'react-redux';
import { Row, Col, Card, CardBody, Button, ButtonDropdown, Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap';
import { Translate } from 'src/components';
import { VCS } from 'src/common';
import { ACCESS_LEVELS, USER_RIGHTS, userAccess } from 'src/constants/user-rights';
import * as Actions from './actions';
import ClientUsersRSuiteTable from './components/client-users-rsuite-table';
import './users.scss';
function Users({ clientId, clientUsers, requestClientUsersData, getUserTemplate, pageParameters, ...props }) {
const [searchValue, setSearchValue] = useState('');
useEffect(() => {
requestClientUsersData({ id: clientId, pageParams: null });
}, []);
const handleChangeSearchValue = (input) => {
const search = input != '' ? input : null;
setSearchValue(search);
};
const [dropdownOpen, setDropdownOpen] = useState(false);
const toggle = () => setDropdownOpen(prevState => !prevState);
return (
<>
<VCS hasRights={[userAccess(ACCESS_LEVELS.EDIT, USER_RIGHTS.API_CLIENTS)]}>
<div className="row">
<div className="col">
<Button
style={{ backgroundColor: '#ffffff !important', color: '#fa5c7c !important' }}
outline
color="danger"
className="mb-2 mr-1 btn-user-template"
onClick={() => getUserTemplate(clientId)}
>
<i className="mdi mdi-file-outline mr-1" size="large" />
<Translate id="pages.client.users.get.user.template" />
</Button>
</div>
</div>
</div>
</VCS>
</>
);
}
Users.defaultProps = {
};
const mapStateToProps = (state) => ({
clientUsers: state.Administration.users.clientUsers ? state.Administration.users.clientUsers :
state.Administration.clients.clientUsers,
pageParameters: state.Administration.users.clientUsersPageParameters ? state.Administration.users.clientUsersPageParameters :
state.Administration.clients.clientUsersPageParameters
});
export default connect(mapStateToProps, Actions)(Users);
My api is:
export const getUserTemplate = async (clientId) => request(`api/clients/${clientId}/users/import/template`, 'GET');
When I click on the button, my api is called two times with same response !
The Api is to export excel data, when I run it, I get :
I want when I run it on clicking the button, I get just one file not two(because it runs two time)
How can I fix it ?

Categories