axios-on-rails, how to request set of records - ruby-on-rails

I'm creating an app in Rails with a ReactJS front-end. In my front-end I'm using the axios-on-rails yarn package to make all my requests to my Rails api back-end.
Heres what I'm trying to do: for the main page of the site I want to implement an infinite scroll feature. For that to work well I need to be able to request small sets of records as the page continues to scroll. The only way I know how to pass records to my front-end is using:
axios.get('/posts.json')
.then((response) => {
...
})
.catch(error => console.log(error));
This returns ALL posts though, which eventually will be thousands. I don't want that happening. So how do I modify this request so that I only get the first 20 records or so?
Answer Details
Okay so I took a second look at pagination as #Gagan Gupta suggested and after a few hours got it to work. Heres what I did.
yarn add react-infinite-scroll to get the component needed.
For my feed component I did...
import React from 'react';
import Post from './Post';
import { connect } from 'react-redux';
import { loadPosts } from '../actions/posts';
import InfiniteScroll from 'react-infinite-scroller';
import axios from 'axios-on-rails';
const node = document.getElementById('owc_feed_payload');
const numberOfPosts = JSON.parse(node.getAttribute('number_of_posts'));
class Feed extends React.Component {
constructor(props) {
super(props);
this.state = {
posts: props.posts,
hasMoreItems: true,
page: 1
};
}
componentDidUpdate(prevProps) {
if (this.props !== prevProps) {
this.setState({ posts: this.props.posts, hasMoreItems: this.props.hasMoreItems });
}
}
loadMore = (page) => {
axios.get('/posts.json', {
params: { page: page }
})
.then((response) => {
console.log(response.data);
this.props.dispatch(loadPosts(response.data));
this.setState({ hasMoreItems: this.state.posts.length < numberOfPosts ? false : true, page: this.state.page + 1 });
})
.catch(error => console.log(error));
}
render() {
let items = [];
this.state.posts.map((post, index) => {
items.push(
< ... key={index}>
...
</...>
);
});
return (
<InfiniteScroll
pageStart={0}
loadMore={this.loadMore}
hasMore={this.state.hasMoreItems}
loader={<p>Loading...</p>}>
{ items }
</InfiniteScroll>
);
}
}
const mapStateToProps = (state) => {
return {
timestamp: state.timestampReducer,
posts: state.postsReducer
}
};
export default connect(mapStateToProps)(Feed);
I used redux to manage the state of my posts. Next I added gem 'kaminari' to my gem file and ran bundle installed then added this line to my controller's index action: #posts = Post.all.order(created_at: :desc).page params[:page] and this to my model: paginates_per 5.
Now it scrolls and loads as expected! Awesome.

The solution would be to use pagination.
Every request will be bring only a set of records you'll specify in the method.
you can perform using gems like will_paginate, kaminari & this is the new gem called as pagy and they claim that it's faster than the other two.
Just increment the page parameter in the url after every request till the last page and you'll get the output you need.
I'm glad my opinion helped you :)
Change your JS code to this:
axios.post('/posts.json', {
params: {
page: page
}
}).then((response) => {
console.log(response.data);
...
}).catch(error => console.log(error));
}

Take a look at console.log(response) after axios then method so you can see the array of objects returning from the server. After then you can set it with .length property of method like:
axios.get('/posts.json')
.then((response) => {
if(response.data.length > 20){
console.log(response.data.slice(0,20))
}
})
.catch(error => console.log(error));

Related

How to add to WebSocket response some additional information?

I am using Dart Alfred framework. And learning websockets.
Here is implamentation from example:
var users = <WebSocket>[];
app.get('/ws', (req, res) {
return WebSocketSession(
onOpen: (ws) {
users.add(ws);
users
.where((user) => user != ws)
.forEach((user) => user.send('A new user joined the chat.'));
},
onClose: (ws) {
users.remove(ws);
users.forEach((user) => user.send('A user has left.'));
},
onMessage: (ws, dynamic data) async {
users.forEach((user) => user.send(data));
},
);
});
https://github.com/rknell/alfred#websockets
I can't figure out how to return some additional data for every user to client.
For example (let's simplify) it's country from server. For example I have next map:
Map cuntries = {
'Mike': 'USA',
'Piter': 'Holland',
'Jow': 'Italy'
};
I did not worked with WebSocket before. Could anybody provide example how to do it?

How to compose fixtures in playwright

I want to compose fixtures. The first fixture should always be available (think of it as base class). second fixture will vary in different test files (think of it as derived class)
I tried following code and it's working as I was expecting. Is this ok to follow this approach or any better option available?
//baseFixture.js
import { test as base} from '#playwright/test';
interface MyFixtures {
fixture1: string;
}
export const test = base.extend<MyFixtures>({
fixture1: "fixture-one"
}, );
//derivedFixture.js
import {test as test1} from 'baseFixture'
interface MyFixtures2 {
fixture2: string;
}
export const test = test1.extend<MyFixtures2>({
fixture2: "fixture-two"
}, );
//in test_file.js
import {test} from 'derivedFixture'
test('should allow me use composed fixture', async ({ page, fixture1, fixture2 }) => {
console.log(`from first fixture ${fixture1}`)
console.log(`from second fixture ${fixture2}`)
});
Seems to me that you are using fixtures like POMs and you are overengineering tests. If it works for you and depending on what you want, then use it. If my assumption is correct instead of passing fixtures to another fixture pass POMs and you can even perform steps here to get that page into certain state and here is example from playwright page:
// my-test.js
const base = require('#playwright/test');
const { TodoPage } = require('./todo-page');
const { SettingsPage } = require('./settings-page');
// Extend base test by providing "todoPage" and "settingsPage".
// This new "test" can be used in multiple test files, and each of them will get the fixtures.
exports.test = base.test.extend({
todoPage: async ({ page }, use) => {
// Set up the fixture.
const todoPage = new TodoPage(page);
await todoPage.goto();
await todoPage.addToDo('item1');
await todoPage.addToDo('item2');
// Use the fixture value in the test.
await use(todoPage);
// Clean up the fixture.
await todoPage.removeAll();
},
settingsPage: async ({ page }, use) => {
await use(new SettingsPage(page));
},
});
exports.expect = base.expect;
Then in your test simply pass {todoPage} or {settingsPage} or both:
const { test, expect } = require('./my-test');
test.beforeEach(async ({ settingsPage }) => {
await settingsPage.switchToDarkMode();
});
test('basic test', async ({ todoPage, page }) => {
await todoPage.addToDo('something nice');
await expect(page.locator('.todo-item')).toContainText(['something nice']);
});
Also you can chain your fixtures here and reuse them, for eg. you could pass todoPage to settingsPage fixture:
settingsPage: async ({ todoPage}, use) => {
await use(new SettingsPage(page));
},
This way everything in todoPage will be executed then settingsPage and this is what you pass to your test, and I guess this is what you were trying to achive.
My approach is to use the base fixture as a dependent fixture in a derivative fixture:
import { test as base } from "#playwright/test"
interface MyFixtures1 {
fixture1: string
}
export const testBase = base.extend<{}, MyFixtures1>({
fixture1: [
async ({}, use) => {
console.log("fixture1 setup once per worker")
use("one")
console.log("fixture1 teardown once per worker")
},
{ scope: "worker" }
]
})
interface MyFixtures2 {
fixture2: string
}
export const test = testBase.extend<MyFixtures2>({
fixture2: async ({ fixture1 }, use) => {
console.log("fixture2 setup for each test")
use(`two-${fixture1}`)
console.log("fixture2 teardown for each test")
},
})
test("should allow me use composed fixture", async ({ fixture1, fixture2 }) => {
console.log(`from first fixture ${fixture1}`)
console.log(`from second fixture ${fixture2}`)
})
test("should base", async ({ fixture1 }) => {
console.log(`from first fixture ${fixture1}`)
})
test("should derived", async ({ fixture2 }) => {
console.log(`from second fixture ${fixture2}`)
})
More info about how to use fixtures in docs

How to update the state when deleting an item using React-Redux and Rails api?

I am trying to create a delete functionality in my application which seems to be deleting the object from the backend just fine, but not in the frontend.
Here is how I structured my project:
in actions/deleteJournal.js
export const deleteJournal = (journal) => {
return dispatch => {
fetch(`http://localhost:3001/journals/${journal.id}` , {
method: "DELETE" })
.then(resp => resp.json())
.then(journal => { dispatch({ type: "DELETE_JOURNAL", journal })
})
}
}
in reducers/journalsReducer.js
const initialState = {
journals: [],
loading: true
}
const journalsReducer = (state=initialState, action) => {
switch(action.type) {
case "LOADING":
return {
...state,
loading: true
}
case "SET_JOURNALS":
return {
...state,
loading: false,
journals: action.journals
}
case "ADD_JOURNAL":
return {
...state,
journals: [...state.journals, action.journal],
}
case 'DELETE_JOURNAL':
return {
journals: state.journals.filter(todo => todo.id !== action.id),
...state
}
default:
return state;
}
}
export default journalsReducer
in components/List.js
import React, { Component } from 'react'
import { connect } from 'react-redux'
import Journal from './Journal'
import { deleteJournal } from '../actions/getJournals'
class ListFiltered extends Component {
render() {
const journals = this.props.journals.map( journal => journal.locationId === this.props.locationId && <Journal key={journal.id} title={journal.title} content={journal.content} id={journal.id} deleteJournal={this.props.deleteJournal} />)
return (
<div>
{journals}
<p></p>
</div>
)
}
}
const mapStateToProps = state => {
return {
journals: state.journals
}
}
export default connect(mapStateToProps, {deleteJournal})(ListFiltered)
in components/Journal.js
class Journal extends Component {
render() {
const { id, title, content } = this.props;
return (
<div>
<ul>
<li>
<h3>{ title } </h3>
<p> { content }</p> <button onClick={() => this.props.deleteJournal(this.props) }>Delete</button>
</li>
</ul>
</div>
)
}
}
export default Journal
So this seems to be giving me an error " Uncaught (in promise) SyntaxError: Unexpected end of JSON input
at deleteJournal.js:48"
I checked my server and it seems to be deleting it from there but nothing in the frontend and when I refresh the item is deleted.
What can I do so it automatically deletes the item from the frontend?
I suspect the issue may be with your line
.then(resp => resp.json())
What is the body of the response when you issue the DELETE request? If it's not a valid JSON object, then JavaScript would throw the error you're seeing.
As you're not using the parsed JSON at all, you could conceivably remove that line:
export const deleteJournal = (journal) => {
return dispatch => {
fetch(`http://localhost:3001/journals/${journal.id}` , {
method: "DELETE" })
.then(journal => { dispatch({ type: "DELETE_JOURNAL", journal })
})
}
}
...but you might want to have some of checking that the DELETE method was executed successfully.
How you do that's up to you and your Rails API, but I'd typically expect to see the response have an HTTP status in the 2xx range when something is successfully deleted, and in the 4xx range if something couldn't be deleted (e.g., if the ID wasn't recognised, or there were dependency issues which meant the deletion request was rejected). That feels outside the scope of this question though.

access data from a rails service in react front end react-on-rails

Quite new to React on Rails apps, especially the React portion. I'm trying to access data in a nested hash that is given from a SQL query in a Rails service. First off, is this even possible?
In Rails Console, lets say user1 has already been found by id, LedgersService.transactions(user1).first returns all data in this format:
{:transactable=>{:type=>"Deposit",
:id=>"28cba04f-5b9d-4c9c-afca-b09a6e0e8739",
:user_id=>"72700244-e6b0-4baf-a381-c22bfe56b022",
:transacted_at=>"2019-03-12 19:04:48.715678", :amount_cents=>15,
:notes=>"none", :processor=>nil, :details=>nil},
:ledgers=>[{:entry_type=>"credit", :amount_cents=>15,
:transacted_at=>"2019-03-12 19:04:48.715678",
:user_id=>"72700244-e6b0-4baf-a381-c22bfe56b022",
:transactable_type=>"Deposit",
:transactable_id=>"28cba04f-5b9d-4c9c-afca-b09a6e0e8739"}]}
I am attempting to do something similar in my React component to try to get the data, however, I'm not quite sure how to set LedgersService.transactions portion. This is how I currently have it:
class LedgersIndex extends React.Component {
constructor(props) {
super(props);
this.state = { ledgers_service: { transactions: [] }, paginator: { count: 0, page: 0, limit: 0 }, user: { id: this.props.match.params.user_id } };
My endpoint call:
componentDidMount() {
var user_id = this.state.user.id;
this.fetchData(user_id, 1);
}
fetchData = (user_id, page_number) => {
apiService.ledgersIndex(user_id, page_number)
.then(
paginated => {
this.setState({
ledgers_service: {
transactions: paginated.ledgers_service.transactions
},
paginator: {
limit: paginated.meta.limit,
count: paginated.meta.count,
page: paginated.meta.page -1
}
});
},
Further down in my render:
render() {
const { classes } = this.props;
const { ledgers_service, paginator } = this.state;
My fetch in apiService:
function locationsIndex(page_number) {
const requestOptions = {
method: 'GET',
headers: Object.assign({},
authorizationHeader(),
{ 'Content-Type': 'application/json' })
};
return fetch(`${process.env.API_SERVER}/api/v1/admin/locations?page=${page_number}`, requestOptions)
.then(handleResponse)
.then(paginated => {
return paginated;
});
}
When I console.log(ledgers_service.transactions(this.state.user.id)), I get the error that ledgers_service.transactions is not a function. console.log(paginator.count) however worked, is this because transactions is being set to an array?
What's the correct way to get that same endpoint in my React component that I got from my rails console?
Quite new to React on Rails apps, especially the React portion. I'm
trying to access data in a nested hash that is given from a SQL query
in a Rails service.
Yes, JS likes JSON so you should have a Rails action that responds with JSON. This is the correct way to exchange data between React and Rails:
# in your React app
fetch('/path/to/resource.json')
.then((returnedResource) => {
// do something with JSON
})
.catch(error => console.error('Error:', error));
# in your controller
respond_to do |format|
format.json { render json: LedgersService.transactions(user1).first }
end
From there, you can treat your returnedResource as a JSON object. In your case, this would be pagination

React Native - Navigate after an async action

I'm developing a mobile app with React Native and Redux and I'm facing a software design problem.
I want to call a REST API (async operation) for login and navigate to main view if that operation was successful.
I'm using redux and thunk so I already have the async actions implemented so my main doubt is: Where should I put the logic to navigate to main view?
Can I access the navigator object directly from an action and perform the navigation there?
Should I do that in the Login Component? (As I'm doing it already - check the code below).
componentWillReceiveProps(nextProps){
if(nextProps.errorLoginMsg){
Alert.alert("Login Failed", nextProps.errorLoginMsg);
}
else if(!nextProps.user.isNull()){
this.props.navigator.replace({name: 'main'});
}
}
I'm not confident of having that logic in the component. Does not seem a good practice. Are there any other way to do this?
Thanks
Here is the code how I do it:
const resetAction = NavigationActions.reset( {
index : 0,
actions: [
NavigationActions.navigate( { routeName: 'Home' } )
]
} );
this.props.requestDeleteEvent( {
id: eventID
} ).then( () => {
this.props.navigation.dispatch( resetAction );
} );
And inside function requestDeleteEvent:
export function requestDeleteEvent(requestData) {
const id = requestData.id;
return (dispatch, getState) => {
return fetch(Config.event + id, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json; charset=UTF-8',
},
})
.then((response) => getResponseJson(response))
.then((responseJson) => {
if (responseJson.isSuccess) {
return dispatch(deleteEvent(id));
}
else {
return dispatch(triggerToast({type: 'alert', content: ERROR}));
}
}
);
}
}
This is one of the hardest problems in react native with the current Navigator API. I would suggest having a route store which holds the current route and have the component which includes the Navigator connected to this store and having a navigation triggered on componentWillReceiveProps.

Resources