Can I use _app.js to share data globally in my app? - javascript

I'm building a small chat app that will use sockets.
Thus I need to use both Context API and a global socket variable. This is my first project using Next.js and I'm not sure how the data flows here.
Beforehand, I used to pass props to the component with React router, however I came to realize that it's not possible with Next.js.
so, my question is whether I'm sharing the socket object correctly.
if that is not the case I would highly appreciate it if you can share with me a code snippet of the right implementation of Sockets utilizing Next.js
My _app.js file:
import { createContext } from 'react';
import '../styles/globals.css'
import UserRoom from '../context/state';
import { useState } from 'react';
function MyApp({ Component, pageProps }) {
const [userName, setUserName] = useState('');
const [userRoom, setUserRoom] = useState('');
const socket = io.connect('http://localhost:4000', { transports : ['websocket'] });
return(
<UserRoom.Provider value={{userName, userRoom, setUserName, setUserRoom}}>
<Component {...pageProps} />
</UserRoom.Provider>
)
}
export default MyApp

The answer is Yes you can, you can share all global states inside _app file, but it will affect to all the components, including the server-side component and the client side, if something depends on the client it will throw an error because window is undefined.
and for the information, this is how next js Data-fetching props flow all work :
Data Fetching > _app > components inside pages
read more here

Related

You're importing a component that needs useState. It only works in a Client Component, but none of its parents are marked with "use client"

The simple below component for handling files uploading is throwing the following error in Next.js's app directory:
You're importing a component that needs useState. It only works in a Client Component, but none of its parents are marked with "use client", so they're Server Components by default.
import { useState } from "react";
export default function FileUploader() {
const [files, setFiles] = useState([]);
return (
<div>
Hello World
</div>
);
}
In the app directory, by default, Next.js uses Server Components, where the JSX gets compiled to "pure HTML" and sent to the browser. Like any traditional Backend with a templating engine, such as Express with EJS, and Laravel with Blade. This is for better performance, as you can read on the doc:
Server Components allow developers to better leverage server infrastructure. For example, large dependencies that previously would impact the JavaScript bundle size on the client can instead remain entirely on the server, leading to improved performance.
And a Server Component shouldn't contain front-end-specific code, for example, hooks such as useState or useEffect. If you need that, your component or one of its parents should have "use client" at the top, to make it a Client Component:
"use client"; // this is a client component 👈🏽
import { useState } from "react";
export default function FileUploader() {
const [files, setFiles] = useState([]);
return (
<div>
Hello World
</div>
);
}

How to retrieve some data from API and use it in all the pages of a NexJS application before rendering on client side?

I have a NextJS application where I have a home page (index.js) and two other pages About(about.js) & Contact Us(contact.js).
I have created a BaseLayour.js file with is wrapping NextJS's MyApp component in _app.js file.
import React from "react";
import BaseLayout from "../layouts/BaseLayout";
function MyApp(props) {
const { Component, pageProps } = props;
return (
<BaseLayout>
<Component {...pageProps} />
</BaseLayout>
);
}
export default MyApp;
This BaseLayout component looks like this -
import React from "react";
import SEO from "../components/SEO";
import Header from "../components/Header";
import Footer from "../components/Footer";
function BaseLayout(props) {
const { children } = props;
return (
<div>
<SEO />
<Header />
{children}
<Footer />
</div>
);
}
export default BaseLayout;
As you can see above in the BaseLayout file, there is an SEO component (React). It contains some common metadata for all the pages. I have an API(api/getmetadata/) that delivers all the metadata in JSON format.
This metadata is supposed to load on the server-side so that the page will be optimized for SEO.
How can we call the API in order to retrieve the data on each request but only on the server-side?
What I have tried till now -
Tried calling API in the SEO component itself, but it is not running on the server-side as it is just a React component.
Tried creating a React context, and called the API from SEO/BaseLayout components, the API call is still not being made from the server-side.
Tried using getServerSideProps in the index.js page to call the API and retrieve the data, which worked perfectly, but the problem is we need to share the data between all the pages, not just the index.js home page.
Any help will be appreciated, If we can somehow make the API call and retrieve the data in the SEO component, it will solve our problem.
Thank you in advance guys.

Send data between components in ReactJs using Apollo Client

According to the documentation, GraphQl provides state management like Redux. I have 2 components. In the Component1 i get data from the server using AppoloClient, it works ok, and in the Component2 i want to read data from the cache(store).
//Component1
import React from "react";
import { gql, useQuery } from "#apollo/client";
const EXCHANGE_RATES = gql`
query GetExchangeRates {
rates(currency: "EUR") {
currency
}
}
`;
const Component1 = () => {
const { loading, error, data } = useQuery(EXCHANGE_RATES);
console.log("component1", data);
return <div>Component1</div>;
};
export default Component1;
//Component2
import React from 'react';
import {gql} from "#apollo/client";
import {client} from "./App";
const Component2 = () => {
const info = client.readQuery({
query: gql`
query EXCHANGE_RATES {
rates(currency: "EUR") {
currency
}
}
`,
});
console.log('component2',info);
return (
<div>
component2
</div>
);
};
export default Component2;
Issue: I can get data in component 1, but when I try to read data from component 2, I get undefined.
Question: How to solve this to be able to read data that is fetched in component 1, in component 2? Also how in GraphQl and Apollo client to pass an object for example in the cache, and to read this in component 1(like redux functionality)?
demo: https://codesandbox.io/s/empty-sun-symv6?file=/src/App.js
When the App mounts, both of your component's data are empty.
Then apollo fetches the data with useQuery. And your component1's state got changed. Because of that, component1 re-render and log the new data.
But there is no state on your component2 that changed. So, component2 does not re-render.
To solve this, you can run useQuery hook with the same query on the component2 again, by default apollo will provide you the data from the Cache.
Apollo provides client-side state handling which can be set up to handle your client site state in the same we you do it with your server-side state.
In your example this is not what you want. Recently there is a noticeable shift in the react community that server side data should not be stored in your global state handling tool of choice. Your fetching tool (in your case apollo) should handle this by caching the responses.
To solve your problem, where both components are using the exact same query, you should just do that. Run the query twice and let apollo handle the caching. So you could pull out the query to a query file or just create a useRates hook and import that in your component to even better share the logic between your components.
To answer why your approach is not working you have to understand that your lookup in the cache is happening at a time before your request has even finished and that this cache look up is not "reactive".
Edit: I just got this out fast to provide a starting point and can clean this up later if things got cleared up.

Why does connected react-redux component not work as normal function, but does as a react child component?

I am trying to create an external functional component, which doesnt need to return any JSX, but just needs to send some internal React state to the Redux store. To keep my App.js clean i was trying to do this in an external file. For some reason the code below, where ChromeStoreToReduxStore is rendered as a child component, does not raise an error.
ChromeStoreToReduxStore.js
import { connect } from 'react-redux';
import * as actions from '../popupRedux/actions'
function ChromeStoreToReduxStore (props) {
console.log(props)
return null
}
export default connect(null, actions)(ChromeStoreToReduxStore);
App.js
function App (props) {
//state to keep track of local chrome store
const [chromeStore, updateChromeStore] = useState({})
...
console.log(ChromeStoreToReduxStore)
...
return (
<div className="App">
<ChromeStoreToReduxStore chromeStore = {chromeStore} />
</div>
);
}
export default App
However, calling the component in App.js like below gives the following error: TypeError: ChromeStoreToReduxStore is not a function
//whenever local store state changes send to redux store
useEffect(() => {
ChromeStoreToReduxStore(chromeStore);
}, [chromeStore])
The console.log below shows it appears to be a react memo. Can anyone explain why i cant call this function wrapped in connect like a normal function, and why it seems to have to be rendered as a React child?
I know i could just connect to the store inside App.js but would like to know why the above does not work.

Is Fetching Data in App.js file allowed in React App?

I have built authorization into my React App using passport.js, and I would like to, in my App.js file, fetch my authorization routes to see if a user is logged into the app, or if nobody is logged in.
To help with the question, I have shared a condensed version of my React App's App.js file, and Index.js file.
// App.js File
// Import React Libraries, Routes, Container Pages
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Route } from 'react-router-dom';
import { userActions } from './actions/auth/auth-actions.js';
import GameLanding from './containers/StatsPages/Game/GameLanding';
import AppFooter from './components/AppFooter';
// And Create The App
class App extends Component {
constructor(props, context) {
super(props, context);
}
componentDidMount() {
this.props.dispatch(userActions.authorize());
}
render() {
return (
<div>
<Route exact path='/stats/games' render={() => <GameLanding userInfo={this.props.userInfo} />} />
<AppFooter />
</div>
);
}
}
// export default App;
function mapStateToProps(reduxState) {
return {
userInfo: reduxState.authorizedReducer.userInfo,
authorized: reduxState.authorizedReducer.authorized,
loading: reduxState.authorizedReducer.loading
};
}
export default connect(mapStateToProps)(App);
... my entire App.js file has ~15 Routes components, and (part of) my goal with my App.js file is to fetch the authorized and userInfo props, and pass these to the components in the various routes. I showed an example where I pass the userInfo prop to the GameLanding component.
Here is how I have set up my Index.js file.
// Index.js
// Import Libraries
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { BrowserRouter } from 'react-router-dom';
// Import CSS and the App
import App from './App';
import 'react-table/react-table.css';
import './index.css';
import './App.css';
ReactDOM.render(
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>,
document.getElementById('root'));
My current problem is as such: For some reason, fetching the userInfo and authorized props is breaking my app. I am unfortunately getting no error messages... rather, all of the react-router-dom Links in my app are simply not working... clicking them changes the url, but the pages of my app no longer change...
My questions are then, (a) am i allowed to fetch authorization data in App.js in the manner I am doing so (using connect, with mapStateToProps, etc.), or am I doing this all wrong?
Whether or not somebody is logged into my app is an app-wide thing, not a page-specific thing, and I figured for this reason (also to prevent having to fetch auth props in many many container pages) that App.js is the best place to grab these props.
Any thoughts on why my app is breaking, or how else my App.js file should look (I am ~99% sure my index.js is fine), would be greatly appreciated! Thanks!
Edit: For reference, doing the following: (i) importing userActions, (ii) calling userActions.authorize() in componentDidMount, (iii) including the mapStateToProps and connect on bottom of app, etc. works for loading the auth props in any of my container components. e.g. if i had this code in my GameLanding component, it doesnt break the react-router-dom Links app-wide in the same manner that it does when this code is in App.js. Hence the title of the question. Thanks!
1) Reason for app breaking:
I am assuming userInfo and authorized props will be undefined, as component renders initially before componentDidMount runs and you have not handled undefined props. You could also pass default props for these props.
2) Better structure for authorization
I am assuming you need to authenticate each route for authorization.
i) Create routes file and enter all routes for your app.
ii) <Route exact path='/stats/games' component={GameLanding} onEnter={reqAuth}/>
Inside reqAuth function you should check if the user is authorized for that route or not.
iii) Inside App component call action for fetching data, store in store and use GameLanding as child component and pass props only when they are defined.
That is not whole code, but should give you gist.
Happy Coding!!!

Categories