I am refractoring an app I've build using React.js. I am exporting a variable from the global scope of Spotify.js and importing it in two other files App.js and Button.js.
After calling a function from Spotify.js that sotres a new value to the variable, It's new value is exported to 'Button.js' but stays an empty string in 'App.js'.
Your help would be appriciated :)
export let userAccessToken = '';
export const Spotify = {
...
getUserAccessToken (){
//stores a new string to userAccessToken.
}
}
import {userAccessToken, Spotify} from '../../util/Spotify';
export class App extends React.Component {
//a conditional rendering happens depending on if(!userAccessToken)
}
import {userAccessToken, Spotify} from '../../util/Spotify'
export class Button extends React.Component {
componentDidMount() {
if (!userAccessToken) {
console.log(`Button's UAT before calling the fn: ${userAccessToken}`)
Spotify.getUserAccessToken();
console.log(`Button's UAT after calling the fn: ${userAccessToken}`);
}
}
...
}
This is not how you share data between react components.
Use react context or pass props between components
You could use react context to share data or simply pass it as props (if the components are closely related in the component tree)
The only thing I can recommend is to export the userAccessToken, something like this, but you can't change its value outside the module
export const Spotify = {
...
getUserAccessToken (){
//stores a new string to userAccessToken.
}
}
...
}
const accessToken = Spotify.getUserAccessToken();
export const userAccessToken = accessToken;
If you got to read this question I solved it.
Turns out I should have called my method Spotify.getUserAccessToken() from App.js using the react lifecycle method componentDidMount().
The export and import methods are like snapshots of the module and therefore when I exported the variable userAccessToke from Spotify.js before calling the my method I imported an empty string and it did not update in all files.
Thanks Jørgen and joseluismurillorios for your support and time spent answering :)
Related
In main.js I have something like this:
import { myUtilFunc} from './helpers';
Object.defineProperty(Vue.prototype, '$myUtilFunc', { value: myUtilFunc });
In this way I have acess to myUtilFunc across whole application with this.$myUtilFunc
But how can I achieve the same in setup() method in Vue 3 if I don't have access to this?
Use provide/inject
Provide
const app = createApp(App);
app.provide('someVarName', someVar); // `Provide` a variable to all components here
Inject:
// In *any* component
const { inject } = Vue;
...
setup() {
const someVar = inject('someVarName'); // injecting variable in setup
}
Note that you don't have to provide from the app root, but can also provide from any component to only its sub-components:
// In *any* component
setup() {
...
},
provide() {
return {
someVarName: someVar
}
}
Original answer
[Edit: While my original answer below is still useful for context properties, it's no longer recommended to use context.root, which is no longer mentioned in the guide and may soon be deprecated.]
In Vue 3, setup has an optional second argument for context. You can access the Vue instance through context.root instead of this:
setup(props, context) {
context.root.$myUtilFunc // same as `this.$myUtilFunc` in Vue 2
}
Things you can access through context:
context.attrs
context.slots
context.parent
context.root
context.emit
While Dan's answer is correct, I would like to provide an alternative mentioned in the comments to the accepted answer. There are pros and cons to each, so, you need to choose based on your needs.
To understand why the code below works, it is important to remember, that provided properties are transitive in the tree of components. I.e. inject('foo') will look for 'foo' in every parent going up the hierarchy all the way to the app; there is no need to declare anything in the middle wrappers.
So, we can write something like this, where globalDateFormatter() is just an example function we want to use in any component down the tree:
main.js
import { createApp } from 'vue'
import App from './App.vue'
const globalDateFormatter = (date) => {
return '[' + date.toLocaleString() + ']'
}
const app = createApp(App)
app.provide('globalDateFormatter', globalDateFormatter) // <-- define here
app.mount('#app')
And then, in some DeepDownComponent.vue:
<template>
<p> {{ fmt(new Date()) }} </p>
</template>
<script>
import { inject } from 'vue'
export default {
setup(){
const fmt = inject('globalDateFormatter', x => x.toString())
// ^-- use here, optional 2nd parameter is the default
return {fmt}
}
}
</script>
Obviously, you can directly import and use provide and inject with the following signatures
provide<T>(key: InjectionKey<T> | string, value: T): void
and
inject<T>(key: InjectionKey<T> | string, defaultValue: T): T
anywhere in your code, doesn't have to be app.provide()
You can also provide values, even the global store, like this, just don't forget to use ref() or reactive() as needed.
In short, whenever you would prefer dependency injection, provide/inject are your friends.
I have started to learn ReactJS, I have a question about this case. const is used before declaration and it doesn't throw error. Why?
import React from "react";
export class item extends React.Component {
render() {
return <div style={customStyle}>test</div>;
}
}
const customStyle = { color: red };
export default item;
The reference to customStyle is inside a function, so it is resolved only when the function is called, which is after all the definitions have been processed.
Using means accessing. If you would construct a new item instance and call it's render method before the const customStyle line, then it would throw.
JS does not semantically enforce that a variable declared with let or const cannot be used before it's initialization, it is just a runtime error to do so.
The same behaviour can be seen if you access variables that don't exist at all:
function see() {
console.log(a); // not a semantical error, this is totally fine ...
}
// ...unless you try to execute it
see();
So, I have ClientContext with default value:
export const defaultContext = {
test: "Hello"
};
export const UserApplicationContext = React.createContext(defaultContext);
And then in my child component I am updating this value:
contextDefaultData = this.context,
contextData = {
...contextDefaultData,
test: "New"
}
};
<UserApplicationContext.Provider value={contextData}>
<App/>
</UserApplicationContext.Provider>
Now, question: In App I can access updated value via UserApplicationContext.Consumer component but I can't access updated value via static like this:
import UserApplicationContext from './UserApplicationContext'
static contextType = UserApplicationContext
So, this.context will point to default value, but not to updated one.
How may I access updated value without exporting new Context?
Thanks!
I guess that's a limitation of the context api?
In order to access the most recent version of the context, you have to be inside of a component that is rendered inside of the provider's component tree and you either have to consume the context like <UserApplicationContext.Consumer> or if you wanna use hooks/functional components, you can do it in a nicer way like const mostRecentContext = useContext(UserApplicationContext).
If you try to import the context like that and access it without a consuming it via a .Consumer or the useContext hook, it will always be whatever the value use passed to React.createContext().
I was trying to learn meteor and not very familiar with this pattern of HOC (it's meteor.js with react).
I was going though their offical docs of tutorials. Here is what they did (You can click here to visit the page)
They imported following package in App.js
import React, { Component } from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import { Tasks } from '../api/tasks.js';
Then there is a simple to do class App extends component wrapped by this HOC
export default withTracker(() => {
return {
tasks: Tasks.find({}).fetch(),
};
})(App);
The official docs for the same says
The wrapped App component fetches tasks from the Tasks collection and
supplies them to the underlying App component it wraps as the tasks
prop. It does this in a reactive way, so that when the contents of the
database change, the App re-renders, as we'll soon see!
Now this language isn't exactly alien to me but I am having hard to comprehend and understand it. So Can someone explain me the same?
To be specific what is The wrapped App component fetches tasks and supplies it to underline app component
A higher order component is in the most basic form a function that takes a component type as input and returns a component class that wraps the input component and adds functionality to it.
Usually the signature is function that takes the argument to apply to the wrapped component which returns a HOC as described above so you can use it with multiple components.
Here is a very basic example that shows an error message if the component it's used on or any of it's child components throw an exception:
const catchError = ({ errorMessage }) => InputComponent => class extends Component {
render() {
try {
return <InputComponent {...this.props} />;
} catch {
return <div>{errorMessage}</div>
}
}
}
const ComponentWithErrorMessage = catchError({ errorMessage: 'Whoops!' })(MyComponent);
// This is the same as the following, the first just immediately invokes the returned function
const whoopsErrorHoc = catchError({ errorMessage: 'Whoops!' });
const ComponentWithWhoopsError = whoopsErrorHoc(MyComponent);
The meteor HOC will be a bit more complicated but the idea is the same. It receives a reference to the meteor Task store and will return a component that re-renders the input component whenever the data changes in the store and add the data to the props of that component.
In my React app, I have a handful of functions that I'd like to be able to access across a few similar components... however, I want to bind this to the shared functions so that they can do things like update the component state, etc... however, it seems that importing the functions and then trying to bind this in the 'typical' React manner does not work.
Here's an illustration of what I'd like to accomplish - in this case, clicking the rendered button would call the function from the imported shared function file and update the component state:
//shared_functions.js
const sharedFunctions = {
testFunction = () => {
this.setState({functionWasRun: true})
}
}
//MyComponent.jsx
import React, { Component } from 'react';
import sharedFunctions from '../static/scripts/shared_functions.js';
let { testFunction } = sharedFunctions;
class MyComponent extends Component {
constructor(props){
super(props);
this.testFunction = this.testFunction.bind(this)
this.state = {
functionWasRun: false
}
}
render(){
return(
<div>
<button onClick={this.testFunction}>Click</button>
</div>
)
}
}
Trying to run this code as is will return an error like:
Uncaught (in promise) TypeError: Cannot read property 'bind' of undefined
and I get what that's all about... but what I'd like to know is: is it possible to bind this to an imported function?
I'm starting to get a lot of similar-looking functions popping up throughout my app and I'd love to simplify things by abstracting them into a shared script, but I'm not sure how to achieve the typical this binding that's needed to achieve state-setting.
The following line is not trying to bind the imported testFunction but rather a method testFunction of <MyComponent>
To bind the imported function, refer to it directly, as follows:
this.testFunction = testFunction.bind(this);
// Notice how: ^--- there is no longer a this here
NB: You're example tries to use bind on an arrow function You cannot bind a new context to an arrow function. The this context of an arrow function will always be set to the location
were it is defined. You can get around this by declaring
testFunction using a regular function declaration:
const sharedFunctions = {
function testFunction(){
this.setState({functionWasRun: true})
}
}
I used it in the following way:
In constructor:
this.handleChange = handleChange.bind(this);
In the imported file (be careful, no arrow):
export const handleChange = function handleChange(event)
{
const { name, value } = event.target;
this.setState({
[name]: object
});
};
import ToastTimeout from 'toastTimout'
ToastTimeout.bind(this)('message to popup')
I was able to get the context of this in a simple service with a setTimeout function that changed the variable on this context
sorry for the sudo code