How to share a class instance across a react application?
I need to use an external library which requires me to instantiate it and use that instance in the application to access various methods/properties,
const instance = new LibraryModule(someInitData);
I am using react functional components in my project. I don't want to re-create instance every time I need to use it (can be thought of a singleton).
How can I share the same instance with all of my components?
Imports are already global, you just need to import the module:
class LibraryModule {
x = 5;
}
const instance = new LibraryModule();
export default instance;
import instance from "./MyLibrary";
import Component from "./Component";
console.log(instance.x);
const App = () => {
useEffect(() => {
console.log(`from App, called last`, instance.x);
}, []);
return <Component />;
};
import instance from "./MyLibrary";
const Component = () => {
useEffect(() => {
instance.x = 10;
console.log(`from component`, instance.x);
}, []);
return <>Component</>;
};
it seems like you are looking for some sort of Dependency Injection,
a thing that isn't really in the react way.
I'd suggest to use the Context Api.
https://reactjs.org/docs/hooks-reference.html#usecontext
import React from 'react';
export const LibraryModuleContext = React.createContext(new LibraryModule);
import React from 'react';
import { LibraryModuleContext } from './library-module';
const App = () => (
<LibraryModuleContextt.Provider>
<Toolbar />
</LibraryModuleContext.Provider>
)
and then later in your code
import React from 'react';
import { LibraryModuleContext } from './library-module';
const FooComponent = () => {
const LibraryModule = useContext(LibraryModuleContext);
return (
<div>Hello {LibraryModule.x}</div>
);
};
Related
In Vue 2 I used to import Vue and access global properties like this (from the store):
import Vue from 'vue'
Vue.config.myGlobalProperty
According to the new documentation, in Vue 3 the global properties are declared using the app object returned by createApp:
const app = createApp({})
app.config.globalProperties.myGlobalProperty
And then accessed in the child component by simply calling this.myglobalProperty
But how to access that global property from the store? I tried exporting/importing the app object but it doesn't work (probably due to the app being created after its import in the store).
With Vue 2 I used to use global properties in the store like this:
Declaration in the main.js file:
import Vue from 'vue'
Vue.config.myglobalProperty = 'value'
Usage in the store:
import Vue from 'vue'
Vue.config.myglobalProperty
Is there a good way to do that in Vue3?
I noticed a better way to provide/inject properties but it works with child component only and not with the store.
You could pass the app instance to a store factory:
// store.js
import { createStore as createVuexStore } from 'vuex'
export const createStore = (app) => {
return createVuexStore({
actions: {
doSomething() {
if (app.config.globalProperties.myGlobal) {
//...
}
}
}
})
}
And use it in main.js like this:
// main.js
import { createApp } from 'vue'
import { createStore } from './store'
const app = createApp({})
const store = createStore(app)
app.use(store)
app.mount('#app')
If your store modules need access to the app instance, you could use the same technique above with a module factory:
// moduleA.js
export default (app) => {
return {
namespaced: true,
actions: {
doSomething() {
if (app.config.globalProperties.myOtherGlobal) {
//...
}
}
}
}
}
And import it into your store like this:
// store.js
import moduleA from './moduleA'
import { createStore as createVuexStore } from 'vuex'
export const createStore = (app) => {
return createVuexStore({
modules: {
moduleA: moduleA(app),
}
})
}
If you do not use Vuex, etc., you can easily create your store via provide/inject on the application itself, as in the example (the example is simplified for understanding):
const createState = () => reactive({counter: 0, anyVariable: 1});
const state = createState();
const key = 'myState';
// example of reactivity outside a component
setInterval(() => {
state.counter++;
}, 1000);
const app = createApp({});
app.provide('myState', state); // provide is used on the whole application
As you can see, your own store can be completely used outside the component.
Inside components, you can use (example):
setup() {
...
const globalState = inject('myStore'); // reactive => {counter: 0, anyVariable: 1}
...
return {globalState, ...}
}
Accordingly, you can have multiple stores in multiple Vue applications.
I hope this example will help you somehow.
I have established a websocket connect from my server to my client machine. I have parsed the data into an object and would like to access the data for representation on my front end.
import './App.css';
import { w3cwebsocket as W3CWebSocket } from "websocket";
import { Component } from 'react';
const client = new W3CWebSocket('ws://xyz:9080/user');
class App extends Component {
componentDidMount() {
client.open = () => {
console.log("Connected");
};
client.onmessage = (e) => {
const object = JSON.parse(e.data);
console.log(object.Snapshot);
}
client.onclose = () => {
console.log("Closed...");
}
}
render() {
return (<div className="App">
<h2>{ object }</h2>
</div>
);
}
}
export default App;
I want to access my object variable from the on message function and use it as a variable in my render function. How do I approach this?
You need to add local state to your class. State is a fairly foundational part of react and how it is able to reactively rerender components, so it sounds like you need to spend some time reading the docs to familiarize yourself with the basics.
That said, I'll provide an updated version of your code for demonstration purposes. Note that you used client.open when you meant client.onopen, so I've made that correction below:
import "./App.css";
import { w3cwebsocket as W3CWebSocket } from "websocket";
import { Component } from "react";
const client = new W3CWebSocket("ws://xyz:9080/user");
class App extends Component {
constructor(props) {
super(props);
this.state = { object: "" };
}
componentDidMount() {
client.onopen = () => {
console.log("Connected");
};
client.onmessage = (e) => {
const object = JSON.parse(e.data);
this.setState({ object: object });
console.log(object.Snapshot);
};
client.onclose = () => {
console.log("Closed...");
};
}
render() {
return (
<div className="App">
<h2>{this.state.object}</h2>
</div>
);
}
}
export default App;
Also, since it seems that you're probably just starting out with react, I would strongly recommend that instead of the old-style class-based components, you use learn to use hooks and functional components, which is just an overall much cleaner and easier to reason about way to write react code. We could rewrite your code as follows using the useState and useEffect hooks in an App function:
import "./App.css";
import { w3cwebsocket as W3CWebSocket } from "websocket";
import { useEffect, useState } from "react";
export default function App() {
const [object, setObject] = useState("");
useEffect(() => {
const client = new W3CWebSocket("ws://xyz:9080/user");
client.onopen = () => {
console.log("Connected");
};
client.onmessage = (e) => {
const newObj = JSON.parse(e.data);
setObject(newObj);
console.log(newObj.Snapshot);
};
client.onclose = () => {
console.log("Closed...");
};
return () => client.OPEN && client.close();
}, []);
return (
<div className="App">
<h2>{object}</h2>
</div>
);
}
Note per the docs that useEffect with an empty dependency array is more or less equivalent to componentDidMount. Note also that even though client is defined in a local scope, it won't be garbage-collected, because it is referenced in the cleanup closure (the return value of the arrow function passed to useEffect).
Finally, note that I haven't used the websocket package before, so I don't know if your usage is correct or optimal. This answer is about how to manage state in react, not how to use websocket in a react application.
import React, { useEffect, useState } from 'react';
import { Card } from 'components/Card';
import { dateFilter } from 'helpers';
import Chart from 'chart.js';
import 'chartjs-chart-matrix';
import chroma from 'chroma-js';
import moment from 'moment';
const WeeklyTrafficCard = (props) => {
const { start, end, data, store } = props;
const capacity = store && store.capacity;
var numberOfweeks = 0; //representing how many weeks back
const dateArray = [];
var today = moment();
while (numberOfweeks < 10) {
var from_date = today.startOf('week').format('MM/DD/YY');
var to_date = today.endOf('week').format('MM/DD/YY');
var range = from_date.concat(' ','-',' ',to_date);
dateArray.push(range);
today = today.subtract(7, 'days');
numberOfweeks++;
//console.log(dateArray);
}
const [each_daterange, setDateRange] = useState();
I have this Component called WeeklyTrafficCard and I want to use the variable, each_daterange, in another component, which imported WeeklyTrafficCard as below to send the get request, clearly I cannot use each_daterange directly right here, how I can work around it?
import React, { useContext, useEffect, useState } from 'react';
import { WeeklyTrafficCard } from './WeeklyTrafficCard';
import { AppContext } from 'contexts/App';
import { API_URL } from 'constants/index.js';
import { todayOpen, todayClose } from 'helpers';
import moment from 'moment';
const WeeklyTrafficCardContainer = (props) => {
const { API } = useContext(AppContext);
const { store = {} } = props;
const [data, setData] = useState([]);
const open = todayOpen(store.hours, store.timezone);
const close = todayClose(store.hours, store.timezone);
useEffect(() => {
async function fetchData() {
const result = await API.get(`${API_URL}/api/aggregates`, {
params: {
each_daterange,
every: '1h',
hourStart: 13,
hourStop: 4
},
});
You should use a useEffect(prop drilling) to pass your variable in your parent:
import React, { Component } from "react";
import { render } from "react-dom";
import "./style.css";
const App = () => {
const [myVar, setMyVar] = React.useState('');
return (
<div>
<Child setMyVar={setMyVar} />
{myVar}
</div>
);
};
const Child = ({setMyVar}) => {
const myChildVar = "Hello world !"
React.useEffect( () => setMyVar(myChildVar),[]);
return <div> This is the child</div>
}
render(<App />, document.getElementById("root"));
Here is the repro on stackblitz
Understanding of the Problem
You want to pass data up to the parent from the child.
Manage each_daterange in the parent:
Instead of creating your useState variable each_daterange in the child you can declare it in the parent and pass down it's setter function. For instance:
const WeeklyTrafficCardContainer = (props) => {
const [eachDateRange, setEachDateRange] = useState();
return (
<div>
{/* your return */}
<WeeklyTrafficCard setEachDateRange={setEachDateRange} />
</div>
)
}
If you need to display eachDateRange in the traffic card, or the traffic card needs to completely own that variable, you can create another state variable in the parent and pass a callback to the child (essentially what is above but now you have two different state variables).
The parent becomes
const WeeklyTrafficCardContainer = (props) => {
const [requestDateRange, setRequestDateRange] = useState();
const updateRequestDateRange = (dateRange) => {
setRequestDateRange(dateRange)
}
return (
<div>
{/* your return */}
<WeeklyTrafficCard updateDateRange={updateRequestDateRange} />
</div>
)
}
Then in your WeeklyTrafficCard call props.updateDateRange and pass it the date range whenever each_daterange changes.
Ciao, of course you need a global state manager. My preferred is react-redux. In few word, react-redux allows you to have a state that is shared in all your components. Sharing each_daterange between WeeklyTrafficCardContainer and WeeklyTrafficCard will be very easy if you decide to use it.
This is the more appropriate guide to quick start with react-redux. have a nice coding :)
Keep the value outside of the component, where both can access it. There are other ways to do this, but just as a simple example you could create a simple "store" to hold it and reference that store from each component that needs it:
class Store {
setDateRange (newDateRange) {
this._dateRange = newDateRange;
}
get dateRange () {
return this._dateRange;
}
}
export default new Store(); // singleton; everyone gets the same instance
import store from './Store';
const WeeklyTrafficCard = (props) => {
// use current dateRange value
const dateRange = store.dateRange;
// set new dateRange
store.setDateRange( newDateRange );
// do other stuff
}
import store from './Store';
const WeeklyTrafficCardContainer = (props) => {
// use current dateRange value
const dateRange = store.dateRange;
// set new dateRange
store.setDateRange( newDateRange );
// do other stuff
}
If you want store updates to trigger component re-renders you'd need to add some higher order component plumbing, like redux's connect, or some other mechanism for triggering updates:
// pseudocode; make store an event emitter and return
// a component that re-renders on store events
store.connect = Component => {
return props => {
React.useEffect(() => {
store.addEventListener( ... )
return () => store.removeEventListener( ... )
})
}
}
Or if the components share a common parent, you could lift the state to the parent and pass the information to each component as props. If either component updates the value, the parent state change will trigger a re-render of both components with the new value:
const Parent = () => {
const [dateRange, setDateRange] = React.useState();
return (
<>
<WeeklyTrafficCardContainer
dateRange={dateRange}
onDateRangeChange={newRange => setDateRange(newRange)}
/>
<WeeklyTrafficCard
dateRange={dateRange}
onDateRangeChange={newRange => setDateRange(newRange)}
/>
</>
);
}
Let's rephrase the objective here.
Objective: access each_daterange from WeeklyTrafficCard component in WeeklyTrafficCardContainer component.
Note: simply put, choose the following case based on your problem.
choose using prop if the variable is to be accessed by only one component
choose using context if the variable is to be accessed by more than one components
Solution Cases:
Case A: using prop.
Case A.1. WeeklyTrafficCard is the parent of WeeklyTrafficCardContainer
each_datarange being passed from WeeklyTrafficCard component as prop to WeeklyTrafficCardContainer component
working example for reference: codesandbox - variable passed as prop
// WeeklyTrafficCard.jsx file
const WeeklyTrafficCard = () => {
const [each_daterange, setDateRange] = useState();
return (
<>
...
<WeeklyTrafficCardContainer eachDateRange={each_daterange} />
</>
);
};
// WeeklyTrafficCardContainer.jsx file
const WeeklyTrafficCardContainer = props => {
const eachDateRange = props.eachDateRange;
return (
<>
...
</>
);
};
Case A.2. WeeklyTrafficCard & WeeklyTrafficCardContainer are children of a parent, say WeeklyTraffic component
each_datarange will be present in WeeklyTraffic component which is shared among WeeklyTrafficCard component & WeeklyTrafficCardContainer component
// WeeklyTraffic.jsx file
const WeeklyTraffic = () => {
const [each_daterange, setDateRange] = useState();
return (
<>
...
<WeeklyTrafficCard eachDateRange={each_daterange} />
<WeeklyTrafficCardContainer eachDateRange={each_daterange} />
</>
);
};
// WeeklyTrafficCard.jsx file
const WeeklyTrafficCard = props => {
const eachDateRange = props.eachDateRange;
return (
<>
...
</>
);
};
// WeeklyTrafficCardContainer.jsx file
const WeeklyTrafficCardContainer = props => {
const eachDateRange = props.eachDateRange;
return (
<>
...
</>
);
};
Case B: using context.
follow blog example found: blog - react context
this is preferred way to implement if the variable/variables is/are shared or need to be accessed by more than 1 components
I need to write a test with the following steps:
get user data on mount
get project details if it has selectedProject and clientId when they change
get pages details if it has selectedProject, clientId, and selectedPages when they change
render Content inside Switch
if doesn't have clientId, Content should return null
if doesn't have selectedProject, Content should return Projects
if doesn't have selectedPages, Content should return Pages
else Content should render Artboard
And the component looks like this:
import React, { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { getUserData } from "../../firebase/user";
import { selectProject } from "../../actions/projects";
import { getItem } from "../../tools/localStorage";
import { getProjectDetails } from "../../firebase/projects";
import { selectPages } from "../../actions/pages";
import Pages from "../Pages";
import Projects from "../Projects";
import Artboard from "../Artboard";
import Switch from "../Transitions/Switch";
import { getUserId, getClientId } from "../../selectors/user";
import { getSelectedProject } from "../../selectors/projects";
import { getSelectedPages, getPagesWithDetails } from "../../selectors/pages";
import { getPagesDetails } from "../../firebase/pages";
const cachedProject = JSON.parse(getItem("selectedProject"));
const cachedPages = JSON.parse(getItem("selectedPages"));
const Dashboard = () => {
const dispatch = useDispatch();
const userId = useSelector(getUserId);
const clientId = useSelector(getClientId);
const selectedProject = useSelector(getSelectedProject) || cachedProject;
const selectedPages = useSelector(getSelectedPages) || cachedPages;
const pagesWithDetails = useSelector(getPagesWithDetails);
useEffect(() => {
dispatch(
getUserData(userId)
);
cachedProject && selectProject(cachedProject);
cachedPages && selectPages(cachedPages);
}, []);
useEffect(() => {
if (selectedProject && clientId) {
dispatch(
getProjectDetails(
clientId,
selectedProject
)
);
}
}, [selectedProject, clientId]);
useEffect(() => {
if (selectedPages && selectedProject && clientId) {
const pagesWithoutDetails = selectedPages.filter(pageId => (
!Object.keys(pagesWithDetails).includes(pageId)
));
dispatch(
getPagesDetails(
selectedProject,
pagesWithoutDetails
)
);
}
}, [selectedPages, selectedProject, clientId]);
const Content = () => {
if (!clientId) return null;
if (!selectedProject) {
return <Projects key="projects" />;
}
if (!selectedPages) {
return <Pages key="pages" />;
}
return <Artboard key="artboard" />;
};
console.log("Update Dashboard")
return (
<Switch>
{Content()}
</Switch>
);
};
Where I use some functions to fetch data from firebase, some to dispatch actions, and some conditionals.
I'm trying to get deep into testing with Jest and Enzyme. When I was searching for testing approaches, testing useEffect, variables, and conditions, I haven't found anything. All I saw is testing if a text changes, if a button has get clicked, etc. but what about testing components which aren't really changing anything in the DOM, just loading data, and depending on that data, renders a component?
What's the question here? What have you tried? To me it seems pretty straightforward to test:
Use Enzymes mount or shallow to render the component and assign that to a variable and wrap it in a store provider so it has access to a redux store.
Use jest.mock to mock things you don't want to actually want to happen (like the dispatching of actions) or use something like redux-mock-store.
Use that component ".find" to get the actual button you want.
Assert that, given a specific redux state, it renders correctly.
Assert that actions are dispatched with the proper type and payload at the proper times.
You may need to call component.update() to force it to rerender within the enzyme test.
Let me know if you have more specific issues.
Good luck!
I have an AuthService class that provides all the api calls and handles authentication for those calls, so its a nice modular service. It is not a React component and not used in any render calls. It handles fetch calls mostly. In many other classes now, I use a single global instance of this class, and import it at the top.
I don't think a context is the right approach because it's not an object type or used in renders. I use the instance in componentDidMount() and useEffect().
an example:
//at the bottom, outside curly braces defining AuthService
export const Auth = new AuthService();
a consumer:
import React, { Component } from "react";
import ReactDOM from "react-dom";
import { useState, useEffect } from 'react';
import CommentList from "./CommentList";
import CommentForm from "./CommentForm";
import Comment from "./Comment";
import AuthService from './AuthService';
import { Auth } from './AuthService';
export default function CommentBox(props) {
const [comments, setComments] = useState([]);
// const Auth = new AuthService();
const [formText, setFormText] = useState('');
const [update, setUpdate] = useState(false);
useEffect(() => {
Auth.fetch('/comments/get_comment_for_bill/' + props.id + '/').then((data) => {
if (typeof data[0] !== 'undefined') {
setComments(data);
} else {
setComments([]);
}
setUpdate(false);
});
return () => {
return;
}
}, [props, update]);
return (
<div >
<CommentList comments={comments}/>
<CommentForm id={props.id} formText={formText} setFormText={setFormText} setUpdate={setUpdate}
onChange={e => {
setFormText(e.target.value);
}} />
</div>
);
}
I think the best approach to using singletons in React is by attaching it to the window object. Just make sure you first attach the singleton to an object, which in turn is attached to the window object - to avoid polluting your window namespace. You would do this attaching in your startup script only once:
index.js
var window.app = {}
window.app.authentication = (function() {
function authenticateUser(userId) {
}
return {
authenticateUser: authenticateUser
}
})();
Then in some other module where you want to use it:
Login.js
window.app.authentication.authenticateUser("johndoe");
It's just fine. There's nothing wrong. But why use same instance for everything?
new AuthService()
I would recommend you to export AuthService. Then, whenever you'll need to use that service, define a new instance and use:
const Auth = new AuthService()
// now, use Auth
It's just a personal choice.