How do I update state when a window variable changes in react - javascript

I have a fairly simple class component that needs access to some data from a service written in vanilla JS. It's simply an interface for the Web MIDI API, that must get access to the MIDI ports, then triggers a callback. I'm importing a function setMidiPorts to the MIDI service then calling it and sending the list of ports on MIDI success. I then need to render those ports in a drop down, but can't seem to get them updated in the component. I've tried passing them down as props from the parent, I've tried importing them directly. Nothing seems to work. I'm pretty new to react so I'm probably doing something pretty wrong, can anyone help me by pointing out where I'm going wrong.
window.inputs = [];
export const setMidiPorts = (inputs) => {
window.inputs = inputs;
console.log(inputs);
};
export default class Preferences extends Component {
constructor(props) {
super(props);
this.state = {
midiInputs: window.inputs,
midiOutputs: [],
};
}
......
EDIT -
I'm looking at this question to see if I can update state from outside, but don't understand how it works properly.
Update component state from outside React (on server response)

Thanks for everyones advice, I managed to solve it by following what it says here.
https://brettdewoody.com/accessing-component-methods-and-state-from-outside-react/
Adding this inside the component render
ref={(Preferences) => {window.Preferences = Preferences;}}
Then I was able to define the setMidiPorts function inside the component and call it from anywhere with window.Preferences.setMidiPorts

Related

How do I generate a stable unique ID in React with SSR?

I'm using SSR in Next.js. I'm trying to generate a unique ID in a component to use in a DOM element's id attribute. This component may be used multiple times on a page so each component instance needs its id to be unique. However, doing something like the following on each render of the component results in a server/client mismatch (component creates new ID on each render, so server and client do not match):
const gradientId = `linear-gradient-${uuid().slice(0, 8)}`
Stuff I've tried:
generating the ID as a default useState value
generating the ID right outside the component (same file, just above component definition)
generating the ID inside a useMemo
All seem to suffer from the client/server mismatch issue. Is there a good way to do this? Is there something stable in the component instance I can base the ID on instead (maybe React generates one I can use?).
I should also note I’m stuck on React 17 so I don’t have access to React 18’s useId, which seems aimed at solving this exact problem!
Any ideas appreciated - thanks!
Well, there may be a better solution out there, but the key appeared to be in abandoning a random ID in favour of a more deterministic ID like an index.
Outside of my component (in the same file, just above the component definition), I can define a function to generate the ID and I can seed it with 0.
let sequentialId = 0
const getSequentialId = function() {
return sequentialId++
}
function MyComponent({ ...otherProps }) {
...etc...
}
then, in my component, I can simply call this instead of the uuid() function I was previously calling:
const gradientId = `linear-gradient-${getSequentialId()}`
This forces the server render to generate 0, 1, 2, etc. and then when the client runs (remember client instances are brand new component instances), it will also start at 0 and sequentially assign IDs in the same order as the server.
I’ve gone one step further and moved this function to a provider where it can be called from any component in case I need this again:
const PageSettingsContext = React.createContext({
_sequentialId: 0,
getSequentialId: function() {
return this._sequentialId++
},
})
function usePageSettings() {
return useContext(PageSettingsContext);
}
function PageSettingsProvider({ ...otherProps }) {
return (
<PageSettingsContext.Provider>
<App />
</PageSettingsContext.Provider>
);
}
export {
PageSettingsProvider,
usePageSettings,
}
This seems to clear up the error and hopefully this holds up over time. Any other ideas or insights welcome but think this works for me.

React setState: Callback to function of child-component

how do I call a function of a child component in the parent component when using setState with a callback function?
Background information
Three components of a simple quiz app:
App.jsx
-- Questions.jsx
-- Results.jsx
To access the answers in the results component AND the questions component, the answers must be saved in the state of the app component.
To modify the state of the app component, each time the user answers a question, I need to call a function that is passed down from the app component to the questions component. After setting the State of the app component, the next question should be displayed. Hence, the function nextQuestion() in the questions component should be called. Normally, I would just write setState({results: results}, nextQuestion()) but since nextQuestion() is a function of the questions component and not of the app component, this does not work.
How can I solve this problem?
Thank you very much!
In the questions component:
checkAnswer = (answer) => {
if (answer !== this.state.solution) {
let s = this.state;
this.props.saveResult(s.question, s.solution[0], answer);
//[...]
}
};
newQuestion = () => {
//[...]
let question = [...this.state.question];
let solution = [...this.state.solution];
const task = taskGenerator.taskDirectory[taskId](); //generate new question + solution
question = task.question;
solution = task.solution;
this.setState({ question, solution });
};
In the app component:
saveResult = (question, solution, answer) => {
let results = cloneDeep(this.state.results)
results.question = question
results.solution = solution
results.answer = answer
this.setState({ results: results }, newQuestion()); //here is the problem; how do I call the newQuestion function of the child component?
};
how do I call a function of a child component in the parent component when using setState with a callback function?
In short, you don't :) Instead, you pass the state from the parent to the child. That's actually the root of your problem with:
but since nextQuestion() is a function of the questions component and not of the app component, this does not work.
In short, there are two "rules" to follow as a React dev for where your state needs to go in your app. To understand them, you have to think of your app as a "tree", with App at the top and all children components, grandchildren components, etc. below ...
The state must be at or above every component that needs to use the state
The state should be as low as possible in your app tree (while still following #1)
It seems you are trying to follow rule #2 and keep your state low (good) ... but your app is breaking that first rule: you have state (nextQuestion relies on this.state.question) which your App depends on. That state is "lower" (in your app tree) than it needs to be, and since it's not at the App-level, App can't use it.
What this means is that you need to move nextQuestion (and the state(s) powering it) into App, so that your code in App can access nextQuestion. Then, you can pass any data that Question needs as props, from App.
With this structure your state (and associated functions like nextQuestion) will live as high as it needs to live (ie. at the App-level), so that all logic that relies on it has access, and any "lower" components (like Question) will simply have that state passed "down the tree" via props.
class App extends Component {
...
setAnswers(answers, callback){
this.setState({results:answers},callback)
}
render(){
return <Questions setAnswers={this.setAnswers}/>
}
}
class Questions extends Component {
onSubmit(answers){
this.props.setAnswers(answers,this.nextQuestion)
}
}
another way would be to react to state change in Questions child component, probably the better way.
If I understand your problem correctly, you should be able to pass the function as props to the child component from the parent. I believe this post answers your question as well.
Call child method from parent
Cheers!

Redux Store Props not Available in time for React Constructor

I have been working with Redux & React for a few months. I usually always use Chrome with no issues. ( Endless bugs actually :) ).
When I started testing in Firefox I ran into an issue which I need some help with ... To know if there is a perfect way at dealing with this ...
Issue
Redux Props for MapStateToProps are not yet available when the constructor gets called, which means I cannot construct my components state in the component constructor. These props become available swiftly afterwards in the render function. At this stage, it is too late because I cannot construct state in the render function (Could somehow work that, but wouldn't be good to approach right ?).
For the moment I am using the componentWillReceiveProps and duplicating my constructor function with one exception
Constructor function
constructor(props){
super(props);
//Loads of code named A
this.state = {state:A};
}
Component Will Receive Props Function
componentWillReceiveProps (){
//Loads of code named A
this.setState({state:A});
}
There may be an issue over overwriting my state here, but for my exact case here, its only displaying data, no UI changes happen... This doesn't appear correct method either way...
I read this article
https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html
I am not quite sure if I understand this fully. I did experiment with it a little with no working solutions.
Ideally, I need the constructor to pause until all redux store is populated which also doesn't make sense. Props arrays could be empty.
There are discussions on Slack but none seem to address this exactly. I tried googling issue but couldn't find exact issue addressed ...
I need the mapStateToProps props to construct my state. It is looking like I won't be able to do this and will need to totally refactor code to work more solely in the render function with loads of ternary operators and/or making calls to set state from the render function before the render returns.
Any thoughts on this issue?
Daniel
Why do you think you need put the data you get from props into the component state?
As far as using the data there is no difference between the two except that you're more likely to get into trouble if you copy props to state (see link you posted).
const { A } = this.state;
const { A } = this.props;
If the data is coming via an async method then you should accommodate that in your render method.
render() {
const { A } = this.props;
if (!A) {
return <LoadingIndicator />
}
...
}

store.getState or mapStateToProps in Component

I have a question that what is the difference between use getState from store directly or use mapStateToProps. Please look at me example below
import React, { Component } from 'react'
import store from '../store'
import { connect } from 'react-redux';
class Test extends Component {
constructor(props) {
super(props);
}
render() {
return (
<p>
<h1>{this.props.count}</h1>
<h2>{store.getState().reducer1.count}</h2>
</p>
)
}
}
const mapStateToProps = (state) => ({
count: state.reducer1.count
});
// export default Test;
export default connect(mapStateToProps)(Test);
Both store.getState and mapStateToProps above work normally, it still updates when state change. If we just use getState only, we don't need to use connect method.
Another point I've recognized is when use mapStateToProps with connect, in reducer we must return a new copy of object state than return that state with modification. If not, component will not update when state changed. Like this:
return Object.assign({}, state, {
count: state.count + 1,
payload: action.payload,
});
But if we use store.getState(), we can either return a new copy or the revised one. Like this:
state.count++;
state.payload = action.payload;
return state
Anyone know please explain to me, thank you.
P/S: and similar with store.dispatch vs mapDispatchToProps, those 2 will work normally, just want to know why we should use mapToProps with connect instead of call the function directly from the store.
mapStateToProps is just a helper function which is really helpful to manage the project in modular style. For example, you can even place all the logic of connect in separate files and use where you want.
Suppose if you're working on a large scale application, then guess a sorts of properties nested there. Using connect you're actually modularizing project which is very helpful for developers who watch the project.
If you don't, you're writing several lines of code in single file.
A possible problem you'll face when using getState() or dispatch() directly. See this post for a little help to make it clear.
The key benefit using connect is that you don't need to worry about when state is changed using store.subscribe(), the connect will let you know each state change whenever it gets updates.
Also, react core concept is based on props and states. Using connect allows you to get redux state as props. Using this.props :)
And ah, I remembered at what condition I accessed the store directly rather than using connect. In my project, I needed to save all the redux state in different form to somewhere and I din't need to connect it to any component. In this case, direct usage with redux store is very easy and helpful. But if we try the same with connect in this case, then we'll have a difficult time.
Thus, I would suggest you to use them in separate condition.
Use connect if you want to map with component.
Access redux store directly if you don't need to map with component.
Further, this blog will explain a bit more: react redux connect explained
Redux Flow:
Using connect with react component:
To conclude: Using connect, you use the provider and it lets the every child component to access the store by providing a provider and using store props in root app component.

ReactJS one time initialisation

I have a Component say HomePage where I'm calling getCurrentLocation to get the location using GPS. This value will be used to select a value from a Drop Down. User may then use the dropdown to change the value.
When I navigate away from this page and then come back using
const appHistory = createHashHistory();
appHistory.goBack();
the constructor and the componentDidMount are executed again. So the user selected value is lost and i get the default value again.
So where do I put by initialisation code? I come from Ionic where there something like ionViewDidLoad which is executed only when the page loads the first time. Is there an equivalent for this is React.
Below is my code
export class HomePage extends React.Component {
constructor(props){
super(props);
state = {
currentLocationCoordinates:[],
};
this.getCurrentLocation = this.getCurrentLocation.bind(this);
}
getCurrentLocation(){
navigator.geolocation.getCurrentPosition((success)=>{
console.log("Current Location: "+success.coords);
this.setState({
currentLocationCoordinates:[success.coords.latitude,success.coords.longitude],
userLocationCoordinates:[success.coords.latitude,success.coords.longitude]
});
});
}
}
componentDidMount(){
this.getCurrentLocation();
}
}
I would avoid using componentDidMount for this. Make your components as resilient to re-renders and re-mounts as possible.
By the sounds of it you are after an application level state container that will hold your application state regardless of whether your component is mounted or not.
Most of the React community relies on Redux for this although other state containers do exist. I'd suggest having a look at Redux and using it to hold those location details as part of Redux store. They will then be always accessible on your component as props, regardless of whether it re-renders or not.
You need to use ComponentDidMount() together with a flag that detects whether the component has been initialized or not. You are on the right track.
componentDidMount(){
initialized ? "" : this.getCurrentLocation();
}

Categories