I am working on a simple React.JS frontend piece.
I essentially have a browsing SPA for historical data. The design has a bunch of filters that I need to populate one at a time, starting the top one in my logic hierarchy.
I do something like:
componentWillMount() {
if (!this.props.advertisers) {
this.props.loadAdvertisers();
}
}
Once advertisers array has been loaded, I map it to options of a select component (using react-select), set the selection to the first item in the list and load the next list - campaigns.
As far as I understand, the best way to do this is still componentWillReceiveProps() and I am a little perplexed how this should be done differently, given that componentWillReceiveProps is being phased out.
My code looks like:
componentWillReceiveProps(nextProps) {
if (nextProps.advertisers && !this.props.advertisers) {
const advertiser = nextProps.advertisers[0];
this.setState({
advertiser: {
value: advertiser['id'],
label: advertiser['name']
}
});
nextProps.loadCampaigns(advertiser['id']);
}
// if campaigns list changed, reload the next filtered array
if (nextProps.campaigns && this.props.campaigns !== nextProps.campaigns) {
...
}
This worked fine, until I decided to add a loading indicator. I mapped state's loading property e.g. for campaigns it gets exposed via this.props.campaignsLoading then do:
return (this.props.campaignsLoading || this.props...Loading || ...) ?
<ProgressIndicator> : <MainContentPanel>
The problem is now, my state does not get set correctly inside componentWillReceiveProps().
The project is using #rematch and I initially tried this with #rematch/loading plugin and when the problem happened, thought the plugin does it wrong, somehow. Then, I mapped loading properties manually, and just added two more dispatches to manually set the loading flag.
All the props are being set/unset correctly, but my state is not being set and nothing works. Any suggestions?
When you do
if (nextProps.advertisers && !this.props.advertisers) {
You are not comparing the next and the previous props. "this.props.advertisers" is probably already set so you never go into the setState line. Although using componentWillReceiveProps is no longer the recommended way to go (You Probably Don't Need Derived State), what you probably want to do roughly is:
if (nextProps.advertisers && nextProps.advertisers !== !this.props.advertisers) {
Related
I have a paginator that determines how many pages to present, based off a data prop that is JSON array's of data from an API.
const itemsPerPage = 3;
const [noOfPages] = React.useState(
Math.ceil(data[0]?.length / itemsPerPage)
);
My issue is, how can I create some sort of conditional to wait for the noOfPages state to fire off before receiving the data prop?
Without ?., I'm getting an undefined issue.
{ data && console.log(data[0].length)} This works just fine, when testing if there is actually data inside the prop. I am not able to re-create this same sort of conditional for my state to wait for the data prop before firing off.
React always renders current state of your application. This means that all state variables exist as long as component that said values attached to exist. Your goal is to orchestrate said state to render what you want to render - that is called "state management".
You can go with different solutions here. For example, you can avoid rendering component until data is loaded, or replace that component with some sort of indication. Or you can set your state to some initial value, like 0 and render null until it will become some positive number. This will not work if it is possible that your value will be 0, but this can be mitigated with another state value. Consider this example:
function Pagination() {
// Here we have one of two possible distinct states: "loading" and "not_loading"
// It is good to use strings for such states, since later you might want to introduce new state, like "error"
let [loading_state, set_loading_state] = useState("loading");
// This is number of pages. It should always exist, but until we are in "not_loading" state we don't actualy care what it would be
let [number_of_pages, set_number_of_pages] = useState(0);
useEffect(function () {
// retrieve data
let data = ...;
// set number of pages and loading state
set_number_of_pages(data[0].length / items_per_page);
set_loading_state("not_loading");
}, []);
if (loading_state === "loading") return null;
if (loading_state === "not_loading") return ....;
}
There would be modifications, depending on where you keep and how you retrieve your data. Sometimes it is even better to calculate values "on the fly", instead of keeping them in state. If there would be some performance heavy computation, you can always use useMemo hook, but this is not your case.
I am trying to grab real-time changes from Firebase on the status of a question in the lifecycle of my system. My child_changed event is interacting differently than I was expecting, so I am not sure if I am using it right.
I want to grab the new change to the questionStatus variable, so that if another user on the system changes the variable on the database, the updates will be seen real time for both users. My Firebase DB is setup like so:
-/questions
-/ID1234567890
-/title
-/messages
-/ID1234567890
-/ID2345678901
'child_changed' call:
fire.database().ref('/questions/' + this.state.questionId + '/questionStatus').on('child_changed', snap => {
this.setState({
questionStatus: (snap.val().questionStatus),
})
console.log("CHANGED!")
})
The above code is how I think it should work, but I can never get the console.log to display as intended. By just removing questionStatus from the ref() (as outlined in the code below), the event triggers whenever I add a new message to the question (for chat), and seems to be causing a loading error. Am I misunderstanding firebase or is deeper in my code? Maybe this has to with lifecycle of component? I have tried placing it everywhere to no avail, but currently have it in componentWillMount(). Let me know what you think, thanks!
fire.database().ref('/questions/' + this.state.questionId).on('child_changed', snap => {
this.setState({
questionStatus: (snap.val().questionStatus),
})
console.log("CHANGED!")
})
Based on my research, I didn't full understand setState() fully, not knowing that the state can only be updated async, not sync. Knowing this, I just implemented a feature informing the user that the status has changed and to click a button to reload. Not ideal, but it will work for now.
I also tested to exclude children with a specific key (messages).
So my new code looks like this:
fire.database().ref('/questions/' + this.state.questionId).on('child_changed', snap => {
// Ignore if messages are being added to the ticket
if(snap.key !== 'messages'){
this.setState({
questionStatus: (snap.val().questionStatus),
questionTags: (snap.val().questionTags),
question
})
}
})
Have kinda a unique question, in my code I have a listener to a database that loads down objects into an array.
All I do when I load it in is
AddObject(obj){
this.setState({
Data: [...this.state.Data, obj]
});
}
Pretty simple. However this listener function, there is no exact time when need data will be added. When I go to use that Data sent in Data, I went to pull it out of the Data Array, however I am worried if I try copying data out of the array, or removing the "seen" data, I will get weird behaivor if my listener function triggers and I try adding data to the array at the same time.
Is there some sort of a way to do this? I guess you could call this a shared resource
Ideally, I would have something like this:
loadDataIN(){
var LengthToGrab = this.state.Data.length;
//we need to remove this length, now any new data will be added to index 0
}
Does this make sense? basically I am trying to figure out the best way to remove data from this array, and not have to worry about overwritting, or losing data. Maybe some sort of processing que
From official doc
setState() enqueues changes to the component state and tells React
that this component and its children need to be re-rendered with the
updated state.
You don't need to worry that two kinds of situation would have conflict in the same time.
setState() enqueues the pending state before the changes be rendered.
In fact, no matter how mechanism be implemented, React is a framework of JavaScript which is working on a model event-loop.
So if you want to pull out the data from this.state.Data:
loadDataIN(){
this.setState(function(prevState, props) {
// this.fetchData = prevState.Data;
return {
Data: []
};
});
}
I'm still pretty new on React development, but I've already work on 3 big project using React+Redux and I see a pattern that I dislike a lot:
componentWillReceiveProps(nextProps) {
if (nextProps.params.type === TYPE_NEW_USER) {
this.modalUsername = this.props.showPopup( < NewUsernamePopup onClose = {::this.closeUsernamePopup
}
/>, USERNAME_POPUP_ID, true);
}
if (this.state.kind !== nextProps.kind || this.state.filter !== nextProps.filter || this.state.hashtags !== nextProps.hashtags) {
this.setState({
results: [],
loading: true,
kind: nextProps.kind,
filter: nextProps.filter,
hashtags: nextProps.hashtags
}, () => this.manageResults(nextProps.results, false));
} else {
this.manageResults(nextProps.results, true);
}
this.managePages(nextProps.paging);
}
I would like to avoid the ifs inside the componentWillReceiveProps. How do you handle it? We've analysed another project using Flux and callback registration. It looks like:
componentWillMount() {
EntityStore.on(EntityActions.ENTITIES_LOADED, this.getData.bind(this));
EntityActions.entitiesLoaded();
}
The first event is emitted by the component, but afterwards the store emits the event and the component updates. Additionally a single store keeps its state and do not duplicate async calls if it already has the content. I personally like to avoid the ifs, but I do NOT want to lose Redux (its community and tools).
How would you add the current logic (ifs) inside the componentWillReceiveProps outside the component? I would like to handle the logic in a service layer and not inside the component.
I would definitely appreciate to read your opinion around this, because I've been struggling to find a solutions that fits.
The redux approach is to put the logic into the actions/reducers.
So i don't know what your manageResults method does, but it is probably the piece of logic you want to move into a reducer so you won't need to call it from your component anymore.
So the kind,filter and hashtagsvariables should be updated from redux actions only.
tl;dr properly following redux best practices would eliminate some of these conditions, but I'd be more concerned about the overall design this snippet is revealing.
To address the individual lines:
if (nextProps.params.type === TYPE_NEW_USER) {
This looks like a redux action was passed to the component? If so, that's not great, only the reducers should care about action types.
this.modalUsername = this.props.showPopup(
The lifecycle hook componentWillReceiveProps is not the right place to initiate things like that, the resulting React component in an instance var also looks quite weird.
if (this.state.kind !== nextProps.kind || this.state.filter (etc.) ) {
If you have UI state in this component that is somehow dependant on the props coming from redux, these types of ifs are somewhat necessary, since you can't do it outside the component.
You are right to dislike this "pattern", which seems to reflect bad overall design. This component seems to be involved with "pages", "results", a username, and some ajax fetching with a loading flag. Can only speculate of course, but it seems like it's doing too much. The ajax request lifecycle should definitely be modelled in a reducer.
That said, the lifecycle hooks do often contain a bunch of ifs, since the reducers don't see routing and which components get mounted/unmounted, so that's where you have to react to changing props sometimes.
I'm trying to conditionally show or not show per say a button based on data that I receive from clicking on a point. I realized that regular jquery functions to add a class don't really work in React. So I figured I could store strings in the state like
this.state: {
hidden_components: {
add_comment: "hide"
}
}
This way I can conditionally show or hide a button by
<button className={this.state.hidden_components.add_comment}> Add Comment </button>
After the render() I have more or less:
componentDidMount() {
this.state.g = new Dygraph
this.state.modal = new Modal
this.state.modal.setContent(use some ID here to reference a div that is hidden but will show up in the modal)
const set_hidden_container = () => {
// I'm just going to use this = notation instead of setState()
// this is supposed to reset the
this.state.hidden_components = "hide"
if (check_comment(this.state.points[at some index].value)) {
this.state.hidden_components = "show"
}
}
this.state.g.updateOptions( {
pointClickCallback: (event, p) => {
console.log("i clicked a point on the graph")
this.setState({
currentPoint: p
})
set_hidden_containers()
// force update
this.setState({
currentPoint: p
})
// I want the modal to open a div of things that only show jsx based on logic in set_hidden_container()
this.state.modal.open()
}
}
componentDidUpdate() {
// logic goes here for like event listeners and anything that queries the DOM after initialization
}
Then in componentDidMount() I have a function that depending on the data received from clicking on a point I do the following:
1) reset all the classes stored in the state to "hide"
2) based on conditions set some of them to "show"
3) concatenate all the classes stored in the state with various styling classes
UPDATE:
I've long since found an easier solution to this problem, however, I'm guessing some people might have similar issues. Therefore, I'll update this question with more psuedocode and a workaround: maybe someone down the line can solve this. This component is particularly frustrating to work with because I haven't been able to make it as modular as I want because of the particular library I'm working with. There are actually about a 1000 lines in this component (I know I know not good).
WORKAROUND:
For those of you who are having trouble with a component's lifecycle in dynamically setting parts of the DOM but don't want to use global variables to set classNames, jquery functions, or use react syntax to show components containing the content I recommend you do the following.
You can still have a set_hidden_container() set content dynamically, you just have to set things based on an id with innerHTML instead of setting a state object to be a string "show". The important thing is, however, that for every time you need to dynamically change content you reset these references to be empty as well as force an update. You can simply change the state of anything and then in componentDidUpdate() you can insert 1) a conditional to check if the innerHTML was actually set or not (since you're not always going to be displaying everything) and 2) within that conditional you can set whatever logic you want associated with the content showing on the page.
componentDidMount is invoked immediately after a component is mounted. If you want to set classNames based on clicks, I would put that logic in componentDidUpdate, which is invoked after updating occurs.