React: Open (.json) file via dialog and read content - html

I can't seem to figure out how to load a local .json file and read the content - so I can dump it into some ´state´.
The code looks like this so far:
import React, { Component } from 'react'
import Files from 'react-files'
class LoadFile extends Component {
render() {
return (
<div className="files">
<Files
className="files-dropzone"
onChange={file => {
console.log(file)
}}
onError={err => console.log(err)}
accepts={['.json']}
multiple
maxFiles={3}
maxFileSize={10000000}
minFileSize={0}
clickable
>
Drop files here or click to upload
</Files>
</div>
)
}
}
export default LoadFile
The logged object does not have any of the data buried inside of it..
[Object]
0: Object
id: "files-1"
extension: "json"
sizeReadable: "288B"
preview: Object
type: "file"

Like #dkniffin said, what behind react-files is DataTransfer.
You could utilize the FileReader API to get the file content and parse it in JSON format, you could see the result in the console section of CodeSandbox below:
constructor() {
super();
this.state = {
jsonFile: {}
};
this.fileReader = new FileReader();
this.fileReader.onload = (event) => {
// or do whatever manipulation you want on JSON.parse(event.target.result) here.
this.setState({ jsonFile: JSON.parse(event.target.result) }, () => {
console.log(this.state.jsonFile);
});
};
}
...
render() {
return (
<div className="files">
<Files
...
onChange={file => {
// we choose readAsText() to load our file, and onload
// event we rigister in this.fileReader would be triggered.
this.fileReader.readAsText(file[0]);
}}
>
Drop files here or click to upload
</Files>
</div>
);
}

Unfortunately, I don't have a complete answer for you. However, after digging into the source code of react-files for a bit, I found this line:
let filesAdded = event.dataTransfer ? event.dataTransfer.files : event.target.files
It appears that filesAdded will be a FileList, which is basically just an array of File objects. Unfortunately, I don't see a way to get the content from a File object. Maybe someone else can help out with that.

Related

issue in json data fetching and rendering in nextjs

I am trying out a small sample in nextjs. All that the proj does is to fetch json data from a file and try displaying it in list of a component. But the behavior is weird. Its getting into infinite loop and I have no clue what's wrong. Could some one take a look at https://github.com/SamplesForMurthy/sampleCode and help me figure out what the issue is? Not able to fetch the data nor I am able to display.
I cloned and fixed. You don't need to use fs.readFileSync here, or fs at all for that matter. You can simply import the .json file as an arbitrarily named variable then map it out.
Here is how I got the data rendering:
import React from 'react';
import testData from '../TestData/SampleData.json';
import SampleParentComponent from '../components/SampleParentComponent';
function TestPage({ filecontent }) {
console.log(`filecontent: ${filecontent}`);
return (
<div>
<SampleParentComponent data={filecontent}></SampleParentComponent>
</div>
);
}
export const getStaticProps = async ctx => {
console.log(ctx.query);
const filecontent = await testData;
return {
props: { filecontent }
};
};
export default TestPage;
/**
* (property) filecontent: {
data: {
seqNo: number;
contactName: string;
}[];
}
*/

Accessing Vuex Store Before Page Load NuxtJS

Context: I am trying to get Google Maps place data via the place_id on the beforeEnter() route guard. Essentially, I want the data to load when someone enters the url exactly www.example.com/place/{place_id}. Currently, everything works directly when I use my autocomplete input and then enter the route but it does not work when I directly access the url from a fresh tab. I've been able to solve this using the beforeEnter() route guard in traditional Vue, but cannot solve for this using Nuxt. Please help!
Question: How can I access the Vuex Store before a page loads in Nuxt?
Error: Any solution I try (see below) I either end up with a blank page or the page will not load (I think it is stuck in a loop and cannot resolve the Promise).
Attempted Solutions:
Using Middleware like below:
middleware({ store, params }) {
return store.dispatch('myModule/fetchLocation', params.id)
}
Using asyncData like below:
data(){
return{
filteredLocation: {}
}
}
// snip
async asyncData({ store, params }) {
const { data } = await store.dispatch('myModule/fetchLocation', params.id)
return filteredLocation = data
}
I tried looking into fetch, but apparently you no longer have access to context
Example Code:
In one of my store modules:
/* global google */
import Vue from 'vue'
import * as VueGoogleMaps from '~/node_modules/vue2-google-maps/src/main'
Vue.use(VueGoogleMaps, {
load: {
key: process.env.VUE_APP_GMAP_KEY,
libraries: 'geometry,drawing,places'
}
})
export const state = () => ({
selectedLocation: {}
})
export const actions = {
fetchLocation({ commit }, params) {
return new Promise((resolve) => {
Vue.$gmapApiPromiseLazy().then(() => {
const request = {
placeId: params,
fields: [
'name',
'rating',
'formatted_phone_number',
'geometry',
'place_id',
'website',
'review',
'user_ratings_total',
'photo',
'vicinity',
'price_level'
]
}
const service = new google.maps.places.PlacesService(
document.createElement('div')
)
service.getDetails(request, function(place, status) {
if (status === 'OK') {
commit('SET_PLACE', place)
resolve()
}
})
})
})
}
}
export const mutations = {
SET_PLACE: (state, selection) => {
state.selectedInstructor = selection
}
}
EDIT: I already have it in a plugin named google-maps.js and in my nuxt.config.js file I have:
plugins: [
{ src: '~/plugins/google-maps.js' }
]
//
//
build: {
transpile: [/^vue2-google-maps.js($|\/)/],
extend(config, ctx) {}
}
Using Middleware is how we can access Vuex before page loads. try putting the configuration part in a custom Nuxt plugin.
Create a file in Plugins folder (you can name it global.js).
Put this
import Vue from 'vue'
import * as VueGoogleMaps from '~/node_modules/vue2-google-maps/src/main'
Vue.use(VueGoogleMaps, {
load: {
key: process.env.VUE_APP_GMAP_KEY,
libraries: 'geometry,drawing,places'
}
})
in global.js.
Then add the plugin in nuxt.config.js like this.
plugins: [
'~/plugins/global.js'
]
Also, make sure you're using underscore before 'page_id' name in your folder structure.

Create-React-App Public Folder JSON

Can't find this exact question answered. I want to have a data.JSON file in my /public folder which contains a static file which once site is built I can quickly modify without having to rebuild the site. However I'm struggling on how to get this into react. I've tried following instructions from the README, but it's a bit unclear.
If I try:
class Home extends Component {
render() {
const data = require(`${process.env.PUBLIC_URL}/data.json`);
I get the message on compile:
./src/Home.js
Module not found: You attempted to import /data.json which falls outside of the project src/ directory. Relative imports outside of src/ are not supported. You can either move it inside src/, or add a symlink to it from project's node_modules/.
What's the proper way to include it? I also tried a hacky way by trying to write it to window.DATA in public/index.html but because I believe it has to call Asynchronous (otherwise chrome gives me an error) sometimes the data will be there, sometimes not, and React doesn't seem to like it. Code I tried:
var request = new XMLHttpRequest();
request.open("GET", "%PUBLIC_URL%/data.json", true);
request.send(null);
request.onreadystatechange = function() {
if ( request.readyState === 4 && request.status === 200 ) {
window.DATA = JSON.parse(request.responseText);
}
}
Any help would be appreciated!
Borrow the "window" variable.
For example, in file "/public/config.js":
window.samleAppConfig = {
entryUrl: "service.com/api"
}
Then in file "/src/App.js":
constructor(props) {
super(props);
console.log('entryUrl', window.samleAppConfig. entryUrl);
}
And in "/public/index.html":
<div id="root"></div>
<script type="text/javascript" src="config.js"></script>
Your first solution will not work if you want to put file in public folder as React should not give access to something outside the source folder. Otherwise you can imagine a bad code could allow people access folder c:\windows
Your second solution could be the way to go, but just need a little bit work on the callback. If you start your project with create-react-app, you can put index.js as
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
var xhttp = new XMLHttpRequest();
var data = {};
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
// Typical action to be performed when the document is ready:
data = JSON.parse(xhttp.responseText);
ReactDOM.render(<App appData={JSON.stringify(data)}/>, document.getElementById('root'));
}
};
xhttp.open("GET", `${process.env.PUBLIC_URL}/data.json`, true);
xhttp.send();
And then your App.js as
import React, { Component } from 'react';
class App extends Component {
render() {
return (
<div>
<h2>{JSON.parse(this.props.appData).Name}</h2>
</div>
);
}
}
export default App;
I put data.json in the public folder, and I have the object like this:
{
"Name": "My App"
}
I tried it just now and it can show My App in the page all the time
You can simply do it like this:
mydata.js
export default {
myStuff: [ "one","two","three"]
}
In your code
import myData from './mydata.js'
You now have your data in a variable called myData

JS import - VueJs Router - having trouble refactoring a watch object

This question is specific to vuejs router, however may simply be a misunderstanding of importing js objects and assigning to the window object.
I am watching for url changes on a page which works fine with the watcher code in the component file. I need to use the same watcher code for multiple components so I extracted it to its own file, assigned it to the global scope, and cannot get it to work. Here are the details:
Working code in with the watcher in the component:
watch:{
$route () {
console.log('route changed')
//was it a reset?
console.log( this.$route.query.sort)
if(this.$route.query.sort === undefined){
if(this.$route.meta.reset){
//reset was pressed... actually do nothing here
this.$route.meta['reset'] = false;
}
else{
this.loading = true;
this.searchableTable.removeResultsTable();
this.searchableTable.options.search_query = this.$route.fullPath;
this.searchableTable.updateSearchPage();
}
}
else
{
//sort change just update the table view
}
}
}
So then I extracted the watch to a file routeWatcher.js:
export default {
$route () {
console.log('route changed')
//was it a reset?
console.log(this.$route.query.sort)
if (this.$route.query.sort === undefined) {
if (this.$route.meta.reset) {
//reset was pressed... actually do nothing here
this.$route.meta['reset'] = false;
}
else {
this.loading = true;
this.searchableTable.removeResultsTable();
this.searchableTable.options.search_query = this.$route.fullPath;
this.searchableTable.updateSearchPage();
}
}
else {
//sort change just update the table view
}
}
}
then I import and use, which works fine....
import searchableTableRouteWatcher from '../../controllers/routeWatcher'
...
watch:searchableTableRouteWatcher
again works fine.
Now the problem - I want to avoid the import in multiple files, so I thought I could put it on the window as a global
in my main.js file:
import searchableTableRouteWatcher from './controllers/routeWatcher'
window.searchableTableRouteWatcher = searchableTableRouteWatcher;
Then in my component:
watch:searchableTableRouteWatcher
results in searchableTableRouteWatcher is not defined
watch:window.searchableTableRouteWatcher
results in no errors, but the code is not being called
I have a feeling it has to do with this and there is confusion on $route()
For your purpose there are 'Mixins' in Vue.js: documentation
What you can do:
create a file, say mixins/index.js:
export const routeWatcher = {
watch: {... your watcher code pasted here ... }
};
import into your component:
import { routeWatcher } from 'path/to/mixins/index';
add mixin to your component properties and methods:
<script>
export default {
mixins: [routeWatcher];
data () ...... all your original component's script content
}
Mixin's content will be merged with component's original properties and act if it was hardcoded there.
Addition after your comment:
You can also declare Mixin globally, like this:
above 'new Vue' declaration put this code:
Vue.mixin({
watch: {....}
});
This mixin will appear in every component.

display json data with react and redux

I am attempting to load some local json data with redux and display in react app. But i'm getting the pageId is undefined in the reducer.
Not sure what I am doing wrong here, I think it might be something wrong with how I'm passing the data but im very new to redux so i'm not sure.
Data
const page = [
{"title":"Mollis Condimentum Sem Ridiculus"},
{"title":"Pharetra Tellus Amet Commodo"}
]
export default page;
Action
const getPage = (pageId) => {
const page = { pageId: pageId }
return {
type: 'GET_PAGE_SUCCESS',
payload: page
}
}
export default getPage
Reducer
import getPage from '../actions/actionCreators'
import pageData from './../data/pageData';
const defaultState = pageData
const pageReducer = (state = defaultState, action) => {
if (action.type = 'GET_PAGE_SUCCESS') {
state.page[action.payload.pageId].title = action.payload
}
return state
}
export default PageReducer
Component
import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux'
import getpage from '../../actions/actionCreators'
const mapStateToProps = (state, props) => {
const page = state.page[props.pageId]
return { page }
}
class Page extends Component {
componentDidMount () {
this.props.getpage(this.props.pageId)
}
render() {
return (<div><PageContainer pageId={0} /></div>)
}
}
const PageContainer = connect(mapStateToProps, { getpage })(page)
export default Page
I've modified your code into a working JSFiddle for reference: https://jsfiddle.net/qodof048/11/
I tried to keep it as close to your example, but let me explain the changes I made to get it working (also note that JSFiddle does not use the ES6 import syntax).
1) Your PageContainer was not constructed correctly. The last parameter should have been a reference to the Page component (not 'page').
const PageContainer = connect(mapStateToProps, { getPageSimple, getPageAsync })(PageComponent)
2) You used PageContainer in the Page component, but PageContainer is the 'wrapper' around Page. You use PageContainer instead of Page in your render method, so it loads the data (maps state and actions).
ReactDOM.render(
<Provider store={store}>
<div>
<PageContainer pageId="0" async={false} />
<PageContainer pageId="1" async={true} />
</div>
</Provider>,
document.getElementById('root')
);
3) The store was mixed up a bit. If I understood your example correctly you want to load a page into the local store from the pageData array, which simulates a server call maybe. In that case you intialState can't be pageData, but rather is an empty object. Think of it like a local database you're going to fill. The call to your action getPage then gets the page (here from your array) and dispatches it into the store, which will save it there.
const getPageSimple = (pageId) => {
const page = pageDatabase[pageId]; // this call would be to the server
// then you dispatch the page you got into state
return {
type: 'GET_PAGE_SUCCESS',
payload: {
id: pageId,
page: page
}
}
}
4) I've added an async example to the JSFiddle to explain how you would actually fetch the page from the server (since the simple example would not be sufficient). This needs the thunk middleware for redux to work (since you need access to the dispatch method in order to async call it). The setTimeout simulates a long running call.
const getPageAsync = (pageId)=>{
return (dispatch, getState) => {
setTimeout(()=>{
const page = pageDatabase[pageId]; // this call would be to the server, simulating with a setTimeout
console.log("dispatching");
// then you dispatch the page you got into state
dispatch({
type: 'GET_PAGE_SUCCESS',
payload: {
id: pageId,
page: page
}
});
}, 2000);
}
}
The JSFiddle loads 2 containers, one with your simple getPage and one with the async version, which loads the title after 2 seconds.
Hope that helps you along on your react/redux journey.
Hey I see a small mistake in you component, I think. You are doing this.props.pageId, when you are setting page and not pageId on the component's props. So shouldn't it be this.props.getPage(this.props.page.pageId) instead? Could that be it?
Also a small side note, an important tip for using redux is to not mutate state. In you reducer where you are doing state.page[action.payload.pageId].title = action.payload you should probably not set state like that, but instead return a new object called newState which is identical to state, but with the title updated. It is important to treat objects as immutable in Redux. Cheers