Tab mismatch issue in primereact - reactjs

The code below is a form in my react app which shows up when a user edits a record from a data table - datatable and edit part is not shown here as it's not relevant.
Explanation of Tab Mismatch issue :
As soon as the below page loads, I see the following tabs (image below) on the UI with Tab1 highlighted:
The network tab of my browser shows the following web service call:
First webservice call for Tab1:
https://myserver.com/MyServices/api/assets/search/getAssetCategories?requestId=100&assetCategoryId=0
Similarly, when I click Tab 2, the web service call is :
Second webservice call for Tab2:
https://myserver.com/MyServices/api/assets/search/getAssetCategories?requestId=100&assetCategoryId=1
And with Tab 3, it's :
Third webservice call for Tab3:
https://myserver.com/MyServices/api/assets/search/getAssetCategories?requestId=100&assetCategoryId=2
As you can see, since I am sending the value of selectedTabIndex for the parameter assetCategoryId in my webservice call in the useEffect function and value of event.index for the parameter assetCategoryId inside onTabSelected function. , the Tab1 is highlighted because of 0 value of selectedTabIndex inside useEffect function and hence the
//Inside useEffect function
params: { assetCategoryId: selectedTabIndex }
//Inside onTabSelected function
params: {assetCategoryId: event.index}
However, since I have the following available:
const assetCategories = JSON.parse('[{"id":1,"value":"Tab 1"},{"id":2,"value":"Tab 2"},{"id":3,"value":"Tab 3"}]');
Question 1.
Inside useEffect function, I want to send first value of the id field to the assetCategoryId parameter instead of the selectedTabIndex while calling the webservice call. Is it possible to do this? I mean I did like this assetCategores[0].id and it worked but is this a good way to achieve this?
Question 2.
I want to keep the activeIndex value based on the first id parameter, which is 1 in my case and not based on selectedTabIndex like it is in my code now:
<TabView id='AssetsTabView' activeIndex={selectedTabIndex}
One another issue which could arise is when the values of id could be like this and I would still want to display Tab1 , Tab2 and Tab3 properly:
const assetCategories = JSON.parse('[{"id":34,"value":"Tab 1"},{"id":66,"value":"Tab 2"},{"id":999,"value":"Tab 3"}]');
My complete code below:
import React, { useState, useEffect } from 'react';
import { Form, withFormik} from 'formik';
import {Button} from '#material-ui/core'
import axios from 'axios'
import {TabPanel, TabView} from "primereact/tabview";
const RequestForm = (props) => {
const {values, setFieldValue, touched, errors, isSubmitting, handleReset, handleChange} = props;
const [selectedTabIndex, setSelectedTabIndex] = useState(0);
useEffect(() => {
if(props.dataRequest){
let editeddataRequestId = props.dataRequest.requestId;
axios
.get('api/assets/search/getAssetCategories?requestId='+editeddataRequestId, {
params: {
assetCategoryId: selectedTabIndex
}
}).then(response => {
}).catch(err => console.log(err));
}//end if of props.dataRequest
}, []);
var onTabSelected = (event) => {
(event.index) ? setSelectedTabIndex(event.index) : null
if(props.dataRequest){
let editeddataRequestId = props.dataRequest.requestId;
axios
.get('api/assets/search/getAssetCategories?requestId='+editeddataRequestId, {
params: {
assetCategoryId: event.index
}
}).then(response => {
}).catch(err => console.log(err));
}
};
const assetCategories = JSON.parse('[{"id":1,"value":"Tab 1"},{"id":2,"value":"Tab 2"},{"id":3,"value":"Tab 3"}]');
const DynamicTabView = ({ activeIndex, onTabChange }) => (
<div style={{width: 'max-content', whiteSpace: 'nowrap', marginLeft: 'auto', marginRight: 'auto'}}>
<TabView id='AssetsTabView' activeIndex={selectedTabIndex}
onTabChange={(e) => setSelectedTabIndex(e.index), (e) => onTabSelected(e)}>
{assetCategories.map((item, i) =>
<TabPanel key={i} header={item.value}></TabPanel>
)}
</TabView>
</div>
);
return (
<div>
<Form className="form-column-3">
<DynamicTabView activeIndex={selectedTabIndex} onTabChange={(e) => {setSelectedTabIndex(e.index), () => { this.onTabSelected(e) }}}/>
<Button size="large" variant="contained" color="primary" onClick={props.onCancel} style={{marginLeft: '5px'}} type="button">Cancel</Button>
</Form>
</div>
)
};
export const DataRequestEnhancedFormEdw = withFormik({
mapPropsToValues: props => {
return {}
},
})(RequestForm)

Related

React get value from key:value array

Beginner question. I know this is a simple question but I haven't been able to get this to work. I'm passing an object which holds an array of k:v pairs to a component. Eventually this props will contain multiple k:v pairs, but for now I'm just passing the one.
[{goal: 20000}]
In the component I'm trying to grab the value, 20000, so I can display it on screen. I can't seem to get just the number. If I look at props.goal I get the entire k:v.
[{goal: 20000}]
If I try props[0].goal I get 'TypeError: undefined is not an object (evaluating 'props[0].goal')'
What am I missing? Thanks for any help.
Update:
Here is the entire code for the component in question.
import { React, useState } from "react";
import Form from "react-bootstrap/Form";
import { Row, Col, Button } from "react-bootstrap";
import "./../css/Goal.css";
const Goal = (props) => {
// const [goal, setGoal] = useState("");
const [record, setRecord] = useState("");
const monthlyGoal = 2;
console.log("props[0]");
console.log(props[0]); //undefined
console.log("props");
console.log({ props }); //See below
props: Object
goal: Object
goals: [{goal: 20000}] (1)
const handleInput = (event) => {
console.log(event);
event.preventDefault();
setRecord(event.target.value);
console.log(record);
};
const defaultOptions = {
significantDigits: 2,
thousandsSeparator: ",",
decimalSeparator: ".",
symbol: "$",
};
const formattedMonthlyGoal = (value, options) => {
if (typeof value !== "number") value = 0.0;
options = { ...defaultOptions, ...options };
value = value.toFixed(options.significantDigits);
const [currency, decimal] = value.split(".");
return `${options.symbol} ${currency.replace(
/\B(?=(\d{3})+(?!\d))/g,
options.thousandsSeparator
)}${options.decimalSeparator}${decimal}`;
};
return (
<Form>
<Row className="align-items-center flex">
<Col sm={3} className="goal sm={3}">
<Form.Control
id="inlineFormInputGoal"
placeholder="Goal"
// onChange={(e) => setGoal(e.target.value)}
/>
<Button type="submit" className="submit btn-3" onSubmit={handleInput}>
Submit
</Button>
</Col>
<Col>
<h1 className="text-box">
Goal: {formattedMonthlyGoal(monthlyGoal)}
</h1>
</Col>
</Row>
</Form>
);
};
export default Goal;
Update 2:Here is the parent component:
import React, { useEffect, useState } from "react";
import Goal from "./Goal";
import axios from "axios";
const Dashboard = () => {
const [dashboardinfo, setdashboardinfo] = useState([]);
useEffect(() => {
async function fetchData() {
try {
const data = (await axios.get("/api/goals/getgoals")).data;
setdashboardinfo(data);
} catch (error) {
console.log(error);
}
}
fetchData();
}, []);
return (
<React.Fragment>
<Goal dashboardinfo={dashboardinfo} />
</React.Fragment>
);
};
export default Dashboard;
If you get an object like the following from console logging destructured props:
{
dashboardinfo: {goals: [{goal: 20000}]}
}
You need to use props.dashboardinfo.goals[0].goal to get the value.
Your props contains the object "dashboardinfo" so you need to do
props.dashboardinfo.goals[0].goal
or a better way is to destructure your props object like this
const Goal = ({dashboardinfo: { goals }}) => {
...
goals[0].goal
...
}
I believe I've resolved my issue. It wasn't so much a problem with accessing the key:value as I thought, because when the page was initialized I was able to grab the value and display it fine. However, when I refreshed the page I lost all of the props data and that resulted in an error. I tracked it down to the useState didn't seem to be updating the value before I was trying to read it. So I added a useEffect in the child component.
const Goal = (props) => {
const [goal, setgoal] = useState([]);
useEffect(() => {
setgoal(props.goal);
console.log("the goal", goal);
}, [props.goal, goal]);
...
This seems to have worked as I'm getting the information I want and not getting any errors when I refresh. This may not be the ideal way to go about this but it is working.

Trigger child rerendering from another child component in reactjs

I'm working on an app with a login page and a main page. In my main page, I have a component A that accepts a request and posts it to a nodejs server, which updates it on a database. Another component B fetches data from the database and displays it. I want to be able to rerender component B when component A's request has been returned with a response.
I understand that I can create a dummy state variable and pass it to B and pass a prop function to A to allow it to change it, but is there a more effective way of doing this?
Code for A's get request:
const onSubmit = async (event) => {
event.preventDefault();
if (vendor === "" || amount === "") {
} else {
const res = await axios.post(`http://localhost:4000/newpayment`, {
vendor,
amount,
});
// want to check res and rerender component B
}
setVendor("");
setAmount("");
};
Code for component B:
import React, { useState, useEffect } from "react";
import axios from "axios";
const ComponentB= () => {
const [payments, setPayments] = useState([]);
const fetchPayments = async () => {
const res = await axios.get(`http://localhost:4000/paymentspending`);
setPayments(res.data);
};
useEffect(() => {
fetchPayments();
}, []);
const onClick = async (id) => {
await axios.post(`http://localhost:4000/markpaid`, { id });
fetchPayments();
};
const renderedPayments = Object.values(payments).map((payment) => {
return (
<div
className="card"
style={{ width: "100%", marginBottom: "20px" }}
key={payment.id}
>
<div className="card-body">
<h4>{payment.vendor}</h4>
<h5>{payment.amount}</h5>
<button
className="btn btn-success"
onClick={() => onClick(payment.id)}
>
Mark as Paid
</button>
</div>
</div>
);
});
return <div className="d-flex flex-row flex-wrap">{renderedPayments}</div>;
};
export default ComponentB;
There are several ways to share the state between components:
Lift the state up.
Add the global store to the app, for example, Redux or Recoil.
If it is a small app, lift the state. Use the Redux if it is a big project.

React setState gives emty array in an axiosRequest

Hi I do have to following simplyfied code. I use Formik as a Validation. Also Material Ui and Reactjs. The Form, Row and Col Tags come from Material. The FastField is same as InputField.
What I want is onClick in the Inputfield a dropdown appears and shows an array which I fetched with the axios-Request.
´´´
const url = 'http://localhost:3000';
const [searchValues, setSearchValues] = useState([]);
const getDropdownItems = (event) => {
console.log('event', event.target.getAttribute('id'));
axios
.get(`${url}/${event.target.getAttribute('id')}`)
.then(
(res) => setSearchValues(res),
console.log('restl', searchValues)
);
};
render(
<Form
onFocus={getDropdownItems}
onSubmit={formik.handleSubmit}
>
<Row>
<Col xs="auto" style={minWidth}>
<FastField
id="DatumEingabe"
name="DatumEingabe"
component={Autocomplete}
label="Datum-Eingabe"
type="text"
options={searchValues}
/>
</Col>
</Row>
</Form>
)
When I check my console I get from the first console.log the name of
the Inputfield. The second console.log says the array is empty,
despite the res is available and should be set. Why does it not work
this way.
setSearchValues(res) will not update searchValues until the next render. If you want to log it each time it changes, you should instead do
const [searchValues, setSearchValues] = useState([]);
useEffect(() => {
console.log(searchValues);
}, [searchValues]);
const getDropdownItems = (event) => {
console.log('event', event.target.getAttribute('id'));
axios
.get(`${url}/${event.target.getAttribute('id')}`)
.then(
(res) => setSearchValues(res)
);
};
I don't think the change is made inmediatly. Try logging searchValues after a second or something like that to see if that is the problem.
const getDropdownItems = (event) => {
console.log('event', event.target.getAttribute('id'));
axios
.get(`${url}/${event.target.getAttribute('id')}`)
.then(
(res) => {
setSearchValues(res);
setTimeout(() => {
console.log('restl', searchValues);
}, 1000)
}
);
};
Also, you have the useEffect hook, which fires an event when a variable is change, so if you want to log it the second it changes you should use:
useEffect(() => {
console.log(searchValues);
}, [searchValues])
To acomplish that, remember to import:
import { useEffect } from "react";
or use
React.useEffect(...)

How do I generate random buttons based on my data coming from API request?

So, I am making a quiz application, in which the questions and answers are fetched from an API using axios.
import React, { useState, useEffect, Component } from 'react';
const axios = require('axios').default;
const PlayQuiz = () => {
// declaring all the state here
const [questionsArray, setQuestionsArray] = useState([]);
// Using effects here
useEffect(() => {
axios({
method: 'get',
url: 'https://opentdb.com/api.php?amount=10',
}).then(res => {setQuestionsArray(Object.values(res.data)[1])})
.catch(err => console.error(err))
}, []);
useEffect(() => {console.log('questions array', questionsArray)}, [questionsArray]);
// Returning html markup here
return (<>
<div className = 'questions-container'>
{questionsArray[0]? <h1>{questionsArray[0].question}</h1> : ''}
</div>
</>)
}
export default PlayQuiz;
This code just basically fetches the data from the API and store it in questionsArray. Using the questionsArray, I print the very first question as a heading, as you can see from this code {questionsArray[0]? <h1>{questionsArray[0].question}</h1> : ''}.
This is how my questionsArray looks like. Now I want to generate 4 dynamic buttons for now that contains the answers in them. Now since there are two values of answers, one correct and other an array of incorrect answers, I am not able to think of how can I print these buttons in a random way, like the correct answer shouldn't be lets say the first option. I want it to be random, i.e. it can be either first button, or second button, third button, or fourth button. How do I achieve it?
You can use a temporary array to store all the answer together (correct and incorrect) and render based on this array. You should put the correct answer in a random position of this array with Math.random() like this:
import React, { useState, useEffect, Component } from "react";
const axios = require("axios").default;
const PlayQuiz = () => {
// declaring all the state here
const [questionsArray, setQuestionsArray] = useState([]);
// Using effects here
useEffect(() => {
axios({
method: "get",
url: "https://opentdb.com/api.php?amount=10"
})
.then((res) => {
setQuestionsArray(Object.values(res.data)[1]);
})
.catch((err) => console.error(err));
}, []);
useEffect(() => {
console.log("questions array", questionsArray);
}, [questionsArray]);
const getAnswers = (q) => {
const temp = [...q.incorrect_answers];
const randomPosition = Math.floor(Math.random() * (temp.length + 1));
temp.splice(randomPosition, 0, q.correct_answer);
return temp;
};
// Returning html markup here
return (
<>
<div className="questions-container">
{questionsArray[0] ? (
<div>
<h1>{questionsArray[0].question}</h1>
{getAnswers(questionsArray[0]).map((a) => {
return <button key={a}>{a}</button>;
})}
</div>
) : (
""
)}
</div>
</>
);
};
export default PlayQuiz;
You can see it working here

How to implement Search Function in React-GraphQL using React-Hooks and Apollo Client?

I try to implement the Search function into my management system using React-Hooks and GraphQL-Apollo Client. While the interface is shown successfully, when I press the 'Search' button it came out an error which named:
Invalid hook call. Hooks can only be called inside of the body of a function component.
I'm pretty sure the useQuery is being called inside a function, so I do not understand what will cause this. The other function such as display all users and add new users are working fine.
I have tried a couple of ways to implement the search function and search around online while still couldn't get it solve. This is my first time encounters React-Hooks too.
Here is my current code in the searchUser component
import React from 'react'
import {useQuery} from 'react-apollo-hooks';
import {searchUserQ} from '../queries/queries';
const SearchUserForm = () => {
let name = '';
let content;
return (
<div id="edit-user">
<div className="field">
<label htmlFor="name">Search UserName</label>
<input type="text" id="name" onChange={ (event) => {
name = event.target.value }}/>
<button onClick={(event) => {
event.preventDefault();
const { data, loading, error } = useQuery(searchUserQ, {
variables: { name: name },
suspend: false
});
if (loading) {
content = <p>Loading User...</p>
}
if (error){
console.log(`Error Occur: ${ error }`);
content = <p>Error Occur!</p>
}
content = data.users.map(user => (
<p key={user.id}>Username: {user.name} ID: {user.id}</p>
));
}}>
Submit
</button>
<p>{ content }</p>
</div>
</div>
)
}
export default SearchUserForm;
Can anyone help with this?
One more question is that my data seems to return undefined everytime I execute the query. Is there something wrong with my query?
Here are the query:
const searchUserQ = gql`
query User($name: String!){
user(name: $name){
id
name
email
}
}
`;
Thanks and appreciate on the help!
According to the Rules of Hooks:
Don’t call Hooks from regular JavaScript functions. Instead, you can:
✅ Call Hooks from React function components.
✅ Call Hooks from custom Hooks (we’ll learn about them on the next page).
If you need to manually call a query manually ins response to an event, use the Apollo client directly. You can use useApolloClient to get an instance of the client inside your component:
const SearchUserForm = () => {
const client = useApolloClient();
...
return (
...
<button onClick={async (event) => {
try {
const { data } = client.query({
query: searchUserQ,
variables: { name: event.target.value },
});
// Do something with data, like set state
catch (e) {
// Handle errors
}
}} />
You can also still use useQuery, like this:
const SearchUserForm = () => {
const [name, setName] = useState('')
const { data, loading, error } = useQuery(searchUserQ, {
variables: { name },
});
...
return (
...
<button onClick={async (event) => {
setName(event.target.value)
...
}} />
You can use the useLazyQuery method and expose your data object to your entire component.
import {useLazyQuery} from '#apollo/react-hooks';
// - etc. -
const SearchUserForm = () => {
// note: keep the query hook on the top level in the component
const [runQuery, { data, loading, error }] = useLazyQuery(searchUserQ);
// you can now just use the data as you would regularly do:
if (data) {
console.log(data);
}
return (
<div id="edit-user">
<div className="field">
<label htmlFor="name">Search UserName</label>
<input
type="text"
id="name"
onChange={(event) => {name = event.target.value }} />
<button onClick={
(event) => {
event.preventDefault();
// query is executed here
runQuery({
variables: { name }, // note: name = property shorthand
suspend: false
})
}}>
// - etc. -
);
}
As opposed to doing the useQuery, the useLazyQuery method will only be executed on the click.
At the point where you are able to pass the 'name' value as a parameter.
If for example you would use the useQuery instead, and have a parameter that is required in your query (i.e. String!), the useQuery method will provide you with an error. Because on component render it will try to run that query directly without the required parameter because at that period of time it's not available yet.
I found problem to my second answer, should just wrap an if-statement before executing it, here is the complete code:
import React, {useState} from 'react'
import {useQuery} from 'react-apollo-hooks';
import {searchUserQ} from '../queries/queries';
const SearchUserForm = () => {
const [name, setName] = useState('');
const { data, error, loading } = useQuery(searchUserQ, {
variables: { name }
});
let content;
let sName;
if (data.user !== undefined && data.user !== null) {
if (loading) { content = 'Loading User...' }
if (error) { content = `Error Occur: ${error}` }
const user = data.user;
content = `Username: ${ user.name } ID: ${ user.id }`
}
return (
<div id="edit-user">
<div className="field">
<label htmlFor="name">Search UserName</label>
<input type="text" id="name" onChange={(event) => {
sName = event.target.value;
}}/>
<button onClick={(event) => {
setName(sName);
}} value={name}>
Search
</button>
</div>
<div>
<p>{ content }</p>
</div>
</div>
)
};
export default SearchUserForm;

Resources