Why only one button? - reactjs

I make a request to my local server using fetch() method. The server returns this response:
{
// total quantity elements in all page
"total":7,
// quantity elements in one page
"perPage":3,
// current page
"page":1,
// quantity page
"lastPage":3,
// it category list. I display my category list on page.
"data":[
{"id":1,"title":"animals","created_at":"/...","updated_at":"/..."},
{"id":2,"title":"space","created_at":"/...","updated_at":"/..."},
{"id":3,"title":"sport","created_at":"/...","updated_at":"/..."}
]
}
Also in my local server, I have ability to use query parameters page or limit which I insert in URL:
page - using this param I can implement pagination
limit - using this param I can implement choose quantity element
I have two tasks:
Make pagination (DONE)
Make ability to choose quantity element on page using three buttons (quantity three elements, quantity four elements, quantity five elements)
First task I've already done, however the second task is where I have a problem.
Instead of three buttons, I have only one button. When I click this button in my page it displays two elements. I also need to show the other 2 buttons which will display three and four elements respectively when clicked.
See screenshot:
What to fix in the code?
Maybe I wrote something wrong in the return?
I comment code line which implement choose quantity element
Home.js:
const Home = () => {
const [value, setValue] = useState({
listCategory: [],
currentPage: 1,
buttonsPagination: 0,
quantityElementPage: 3, // this line
buttonsQuantityElementPage: 3 // this line
});
useEffect(() => {
async function fetchData(currentPage, quantityElementPage) { // this line
try {
const res = await apiCategory('api/categories', {
method: 'GET',
}, currentPage, quantityElementPage ); // this line
console.log(res);
setValue({
listCategory: res.data,
currentPage: res.page,
buttonsPagination: Math.ceil(res.total / res.perPage),
quantityElementPage: res.perPage, // this line
});
} catch (e) {
console.error(e);
}
}
fetchData(value.currentPage, value.quantityElementPage); // this line
}, [value.currentPage, value.quantityElementPage]); // this line
const changePage = (argPage) => {
setValue((prev) => ({
...prev,
currentPage: argPage,
}));
};
const changeQuantityElementPage = (argElement) => { // this method
setValue((prev) => ({
...prev,
quantityElementPage: argElement,
}));
};
return (
<div>
<Table dataAttribute={value.listCategory} />
{[...Array(value.buttonsPagination)].map((item, index) => (
<button key={'listCategory' + index}
onClick={() => changePage(index + 1)}>{index + 1}
</button>
))}
//here I display button who choose quantity element:
{[...Array(value.buttonsQuantityElementPage)].map((item, index) => (
<button onClick={() => changeQuantityElementPage(index+2)}>quantity element - {index+2}
</button>
))}
</div>
);
};
apiCategory.js:
export const apiCategory = async (url, args, valuePage, valueElement) => { //add valueElement in argument
const getToken = localStorage.getItem('myToken');
const response = await fetch(`${apiUrl}${url}?page=${valuePage}&limit=${valueElement}`, { //add valueElement in param limit
...args,
headers: {
"Content-type": "application/json; charset=UTF-8 ",
"Authorization": `Bearer ${getToken}`,
"Accept": 'application/json',
...args.headers,
},
});
return response.json();
}

This is happening because your value.buttonsQuantityElementPage is undefined. When you call setValue in useEffect, you did not include the previous values. buttonsQuantityElementPage no longer exists on value.
Also, if the Array constructor Array() receives an undefined value, it will return an array with a single undefined value. i.e. [...Array(undefinedValue)] will yield [undefined]
There are two options for a fix.
Option 1. Update the setValue in your useEffect to include all previous values and then override the properties with new values.
setValue(prev => ({
...prev,
listCategory: res.data,
currentPage: res.page,
buttonsPagination: Math.ceil(res.total / res.perPage),
quantityElementPage: res.perPage,
}));
Option 2. Split out the buttonsQuantityElementPage into its own variable. As far as I can see in your code, buttonsQuantityElementPage does not change. If that is the case then use
const buttonsQuantityElementPage = 3
and in the return section
[...Array(buttonsQuantityElementPage)].map((item, index) => (
<button onClick={() => changeQuantityElementPage(index + 2)}>quantity element - {index + 2}
</button>
))

Related

RTK query use infinity scroll and pagination at the same time

Describtion:
I am trying to use infinity scroll and pagination at the same time.
I have a component which loads the user orders with infinity scroll and another component in different page which loads user order by pagination.
after i read the documentation I noticed we could use serializeQueryArgs, merge, forceRefetch to create these effects so I did it in my api slice.
Problem:
I have nice infinity scroll but my pagination works just like my infinity scroll
every time I click on a page the new data merged with my prev page.
so I decided to use extera prams for my query just only for serializeQueryArgs mehtod and check if I call this api from the component with infinity scroll or pagination and it works better and now i have another problem for example when I'm in the page 1 and go to page 2 everything seems normal but when i back to page 1 i have duplicated the page one array.
here is my api Slice:
export const orderApi = ApiSlice.injectEndpoints({
tagTypes: ['Orders'],
endpoints: (builder) => ({
getOrdersCount: builder.query({
query: ({ page, size, other }) => ({
url: `/order/orders/count?${page ? `page=${page}` : ''}${
size ? `&size=${size}` : ''
}${other || ''}`,
method: 'GET'
}),
transformResponse: (res) => res // just only one number
}),
getOrderList: builder.query({
providesTags: (result) => {
return result.map((item) => ({ type: 'Orders', id: item.id }));
},
async queryFn({ page, size, other }) {
const portfolioList = await axios.get(
`/order/orders?${page ? `page=${page}` : ''}${size ? `&size=${size}` : ''}${
other || ''
}`
);
if (portfolioList.error) return { error: portfolioList.error };
const endpoints = portfolioList.data.map((item) =>
axios.get(`/market/instruments/${item.insRefId}`)
);
let error = null;
let data = null;
try {
data = await Promise.all(endpoints).then((res) => {
return res.map((item, index) => {
return { ...item.data, ...portfolioList.data[index] };
});
});
} catch (err) {
error = err;
}
return data ? { data } : { error };
},
// Only have one cache entry because the arg always maps to one string
serializeQueryArgs: ({ endpointName, queryArgs }) => {
const { infinity, page, size } = queryArgs;
if (infinity) return endpointName;
return endpointName + page + size;
},
// Always merge incoming data to the cache entry
merge: (currentCache, newItems) => {
currentCache.push(...newItems);
},
// Refetch when the page arg changes
forceRefetch({ currentArg, previousArg }) {
console.log('currentArg', currentArg);
return currentArg !== previousArg;
},
})
})
});
any solution or best practice ?

Recoil refresh state not working of that state has been altered

I'm trying to reset my state by calling the APi to get the latest data. The API call returns an array of object.
export const launchesState = atom<Launch[]>({
key: 'launchesStateLatest',
default: selector<Launch[]>({
key: 'launchesStateInit',
get: async () => {
const response = await getLaunches();
return response.data.map(launch => ({ ...launch, isSelected: false }));
},
}),
});
I'm using a selectorFamily to select each object when the list is rendered.
export const launchState = selectorFamily<Launch | undefined, string>({
key: 'launchState',
get:
missionName =>
({ get }) => {
return get(launchesState).find(item => item.mission_name === missionName);
},
set:
missionName =>
({ get, set }) => {
const currentState = get(launchesState);
const index = currentState.findIndex(item => item.mission_name === missionName);
set(
launchesState,
produce(currentState, draft => {
draft[index].isSelected = !currentState[index].isSelected;
}),
);
},
});
The UI contains a checkbox for each item in the array and when click it uses the set function in the selectorFamily ti update the launchesState.
I'd like to refresh the data using:
useRecoilRefresher_UNSTABLE(launchesState)
Which works ok if the data has never been altered, or is reset using useResetRecoilState(launchesState), but if I have clicked any of the checkboxes and altered the state then the state isn't refreshed.
Can someone help me understand why this is happening, is it a bug or is this happening for a reason?

run function once a value in an object has been updated

I have a list of objects that have multiple fields. I want to make an api call once the user has entered the location of the plant.
I thought of using useEffect, but that won't work since in order to make the api call, I need two values - id and index, and (as far as I know) you can't pass parameters in useEffect. I did it normally (which you can see in the code below), instead of using useEffect, but that doesn't work because the plant location is empty (as there is a bit of a delay before the plant location is updated).
So, how can I make the call once the plant location has been updated?
//the plants object
const [plants, setPlants] = useState([
{
name: 'Plant 1',
id: uuidv4(),
location: '',
isOffshore: false,
coords: {},
country: '',
}
])
const handlePlantLocChange = (id, index, value) => {
setPlants(
plants.map(plant =>
plant.id === id
? {...plant, location: value}
: plant
)
)
handler(id, index);
}
// this makes the api call.
//I need to pass id and index to this function
//no matter what, hence why I can't use useEffect
const getCoords = (id, index) => {
axios.get('http://localhost:3001/endpoints/coords', {
params: {
location: plants[index].location
}
}).then((response) => {
if(response.status === 200) {
handlePlantInfoChange(id, PlantInfo.COORD, response.data)
}
})
}
const handler = useCallback(debounce(getCoords, 4000), []);
return (
<div className='plant-inps-wrapper'>
{
plants.map((item, idx) => {
return (
<div key={item.id} className="org-input-wrapper">
<input placeholder={`${item.isOffshore ? 'Offshore' : 'Plant ' + (idx+1) + ' location'}`} onChange={(e) => handlePlantLocChange(item.id, idx, e.target.value)} value={item.isOffshore ? 'Offshore' : item.location} disabled={item.isOffshore} className="org-input smaller-input"/>
</div>
)
})
}
</div>
)
you can pass props and state to useEffect like this:
useEffect(() => {
getCoords(id, index);
return () => getCoords(id, index);
}, [id, index])
Please use useEffect hook with dependency array.
Like below:
useEffect(() => {
// You can call API here because When plant state updated this will be called.
}, [plants]);

How to set state based on different functions in react using firebase

I am trying to make a template system in my survey tool and set the state to a json object which will be pushed onto the firebase database, but I want to know how to change the state based on which template i choose in the previous page:
I have 4 templates:
Blank
Board
Board Member
Chairperson
I am showing these as buttons and when any of them is clicked the corresponding handlers run
<button
onClick={function(event) {
props.clicked();
notify();
}}
className={classes.Blank}
>
Blank Survey
</button>
<button onClick={props.board} className={classes.Board}>
Board Survey
</button>
<button onClick={props.chair} className={classes.Chair}>
Chairperson Survey
</button>
<button onClick={props.member} className={classes.Member}>
Board Member Survey
</button>
Now I use the props to assign them to corresponding handlers:
<NewSurveyView
clicked={this.clickHandler}
board={this.boardHandler}
chair={this.chairHandler}
member={this.memberHandler}
isLoading={this.props.loading}
error={this.props.isError}
/>
These are the three functions I am using to fetch the templates:
const fetchBoardMember = async () => {
const res = await axios.get(
"/surveys/ckb0kmt3n000e3g5rmjnfw4go/content/questions.json"
);
return res.data;
};
const fetchBoard = async () => {
const res = await axios.get(
"/surveys/ckb24goc800013g5r7j8igrk3/content/questions.json"
);
return res.data;
};
const fetchChair = async () => {
const res = await axios.get(
"/surveys/ckb24gt3s00053g5rrf60353r/content/questions.json"
);
return res.data;
};
Next, I create an object with all the info which i want to push (this is for Board Template):
const board = {
title: "Your Survey Title",
subTitle: "Your Survey Description",
creatorDate: new Date(),
lastModified: new Date(),
questions: fetchBoard().then(questions => {
this.setState(state => ({
...state,
content: {
...state.content,
questions
}
}));
}),
submitting: true
};
Setting the state:
this.setState({
_id: newId(),
content: board,
userId: this.props.user_Id
});
Now my question is, how do I change the variable used based on the template chosen by using the buttons?

How to paginate data?

How can I implement pagination to display 10 objects on a page? Here's my sandbox:
https://codesandbox.io/s/rvo801wyp
There's two things to do:
You need to either bind handlePageChange in the constructor or convert it to an arrow function
Add a slice to remove from items the data that's not going to be shown .slice(this.state.activePage * this.state.itemsCountPerPage,(this.state.activePage + 1) * this.state.itemsCountPerPage)
Take a look to my changes in the code:
https://codesandbox.io/s/k58z3r9115
I've also added dynamically itemsCountPerPage to the state
There are two ways to achieve this objective,
Pagination using API
make API to handle some request param like
{
**per_page_limit**: <no of record on a single page>,
**current_page**: <current page number>,
....other request param
}
Pagination on client end
fetch all the records and create a pagination, for this get length of list and on bases of your requirement create pages,
for eg. total record: 100
per page limit: 10, so total pages will be 10
now to show only data for page, use setState, and for data use
list_data_to_display = list.slice(0, page*limit)
this.setState({slow_list: list_data_to_display})
handlePageChange=(pageNumber)=> {}
Solution will be:
this.state = {
items: [],
isLoading: false,
activePage: 1,
itemsCountPerPage: 1,
totalItemsCount: 1,
**show_list**: []
};
show_list_data = () => {
const show_list_data = this.state.items.slice(0, 10);
this.setState({
show_list: show_list_data
});
};
componentDidMount() {
fetch("https://demo9197058.mockable.io/users")
.then(res => res.json())
.then(json => {
this.setState(
{
items: json,
isLoading: true
},
this.show_list_data
);
});
}
handlePageChange = pageNumber => {
console.log(`active page is ${pageNumber}`);
const show_list_data = this.state.items.slice(
(pageNumber - 1) * 10,
pageNumber * 10
);
this.setState({
show_list: show_list_data,
activePage: pageNumber
});
};
render() {
var { show_list, isLoading } = this.state;
if (!isLoading) {
return <div> loadding....</div>;
} else {
return (
<div>
<ul>
{show_list.map(item => (
....
....

Resources