Navigation Experimental - Replace NavigationHeader Title Within a Scenes View - reactjs

I'm trying to update the NavigationHeader title (along with left and right components) from a specific scene's view.
Example:
User navigates to profile view
Get data from server
Call redux action to update the navigation header with the username
My current approach is not working too well. I'm trying to use the NavigationStateUtils to replace the scene with an updated version of itself. This works if my view is the first one in the stack. However, if I try adding the call to update nav header on a route that isn't the first, it will freeze because the navigation hasn't finished it's animation.
I could try adding a timeout on the call, but it seems hacky to come up with a delay that makes sense globally.
case UPDATE_NAV_HEADER: {
const tabs = state.get('tabs')
const tabKey = tabs.getIn(['routes', tabs.get('index')]).get('key')
const scenes = state.get(tabKey).toJS()
const route = scenes.routes.slice(-1)[0]
var leftComponent = null
if (action.payload.leftComponent) {
leftComponent = action.payload.leftComponent
} else if (route.navLeftComponent) {
leftComponent = route.navLeftComponent
}
var rightComponent = null
if (action.payload.rightComponent) {
rightComponent = action.payload.rightComponent
} else if (route.navRightComponent) {
rightComponent = route.navRightComponent
}
const newScene = NavigationStateUtils.replaceAt(
scenes,
route.key,
{
key: route.key,
title: action.payload.newTitle ? action.payload.newTitle : route.title,
navLeftComponent: leftComponent,
navRightComponent: rightComponent,
shouldRenderHeader: "shouldRenderHeader" in route ? route.shouldRenderHeader : true,
shouldRenderTabBar: "shouldRenderTabBar" in route ? route.shouldRenderTabBar : true
}
)
return state.set(tabKey, fromJS(newScene))
}

Related

How can I use gmail-js to add my extension button in the toolbar?

I need to add my chrome extension button to the toolbar, I found the Gmail-js
gmail.tools.add_toolbar_button('<div id="icon_placeholder"></div>', function () { }, 'temp_css')
and it adds the button but if I go to another folder, it vanishes (if I inspect it, the element is still there, just invisible).
Another problem is if I start from inside a message or from another folder (hard refreshing the page) and then going to inbox, it won't show up.
How can I use the gmail-js to render and persist the button there? it'd be ok if it only shows up on inbox, as long as it's there (on inbox) no matter what folder start browsing from
I tried just rendering the button and checking to see if:
The element is not present in the page, if so, render it (doesn't seem to be working)
Check if the page was refreshed, if so, render (doesn't seem to be working)
Here's a piece of the code I have so far:
const gmail = new GmailFactory.Gmail() as Gmail;
var btn = gmail.tools.add_toolbar_button('<div id="icon_placeholder"></div>', function () { }, 'temp_css').get(0)['className'];
const getElement = document.querySelectorAll('.' + btn.toString().replace(' ', '.'))[5]
console.log('getElement ' + getElement.getAttribute('class'))
var app: HTMLElement = document.createElement('div') as HTMLElement;
var pos: HTMLElement = getElement as HTMLElement;
if (pos !== null) {
pos.appendChild(app);
ReactDOM.render(<IconExtChrome />, app);
}
function refreshButton(): boolean {
if (!getElement) {
pos.appendChild(app);
ReactDOM.render(<IconExtChrome />, app)
return false
}
}
if (sessionStorage.getItem('reloaded') != null) {
console.log('page was reloaded');
pos.appendChild(app);
ReactDOM.render(<IconExtChrome />, app)
} else {
console.log('page was not reloaded');
}
sessionStorage.setItem('reloaded', 'yes');
refreshButton()```

React - useState appending to the array changes previously appended objects

Hi all,
I have a small react app that is creating (mapping from an array) new tabs (and panels) when there is a new message over the websocket.
There is an initial setup, that is hardcoded for the test purposes which sets up 2 tabs on load, any new ones should be appended to these two.
const INITIAL_ARRAY= [
{
id: 1,
child_component_config: {...}
},
{
id: 2,
child_component_config: {...}
}
];
const template = {
child_component_config: {...}
}
The code, simplifed:
const [current_array, setNewArray] = useState( INITIAL_ARRAY);
export default function ParentComponent() {
useEffect(() => {
const client = new ws...
client.onConnect = function (frame) {
var message = clientDesk.subscribe( '/topic/desks/4', function (message) {
// on message
var new_tab = TEMPLATE;
new_tab.id = Math.max( ...current_array.map( elem => elem.id ) ) + 1;
setActiveTab(new_tab.id);
setNewArray([...current_array, new_tab]);
}
}
}, [ current_array ]);
const tabs = map tabs
cosnt panels = map panels
return(
{tabs}
{panels}
)
The problem:
On first message from the WS the third element is added to the array properly (example-1)
, fourth one is added properly but it also overwrites the third element (example-2)
making them exactly the same. After fourth it gets strange, where some are overwritten and some are not.
I've tried:
Moving the state updating out of useEffect or removing useEffect completly
Storing current_array in a temp var before any logic
Adding a counter to track which tab's id is the latest -> tracking state of just one number works
const [tab_count, setTabCount] = useState( INITIAL_ARRAY.lenght );
Using counter to try to force rendering
Setting up a fixed number of objects in the initial array and just update which ever is needed (with and without counter)
Updating based on the previous value
setNewArray( prevArray => {
logic
return [...prevArray, new_tab];
}
After the first WS message, if the code is changed/saved and webpack compiled, the next message will add a new element to the array properly.
EDIT - Solved:
Managed to solve this by building a new object (instead of using the template) before adding it to the array.

React, dynamically add text to ref span

I'm trying to render a message to a span tag specific to an item in a list. I've read a lot about React 'refs', but can't figure out how to populate the span with the message after it's been referenced.
So there's a list of items and each item row has their own button which triggers an API with the id associated with that item. Depending on the API response, i want to update the span tag with the response message, but only for that item
When the list is created the items are looped thru and each item includes this
<span ref={'msg' + data.id}></span><Button onClick={() => this.handleResend(data.id)}>Resend Email</Button>
After the API call, I want to reference the specific span and render the correct message inside of it. But I can't figure out how to render to the span at this point of the code. I know this doesn't work, but it's essentially what I am trying to do. Any ideas?
if (response.status === 200) {
this.refs['msg' + id] = "Email sent";
I recommand using state. because string refs legacy (https://reactjs.org/docs/refs-and-the-dom.html#legacy-api-string-refs)
const msgs = [
{ id:1, send:false },
{ id:2, send:false },
{ id:3, send:false },
];
this.state = {
msgs
};
return this.state.msgs.map((msg, index) => {
const status = msg.send ? "Email Sent" : "";
<span>{ status }</span><Button onClick={() => this.handleResend(index)}>Resend Email</Button>
});
async handleResend (index) {
const response = await callAPI(...);
if(reponse.status !== 200) return;
const newMsgs = _.cloneDeep(this.state.msgs);
newMsgs[index].send = true;
this.setState({
msgs: newMsgs
})
}
The workaround is set innerText
this.refs['msg' + id].innerText = "Email sent";
But rather than using ref try to use state to update elements inside render.
i was facing with this issue right now and i figured it out this way:
// currentQuestion is a dynamic Object that comes from somewhere and type is a value
const _target = `${currentQuestion.type}_01`
const _val = this[`${_target}`].current.clientHeight // here is the magic
please note that we don't use . after this to call the ref and not using refs to achieve what we want.
i just guessed that this should be an Object that would hold inner variables of the current object. then since ref is inside of that object then we should be able to call it using dynamic values like above...
i can say that it worked automagically!

ExtJS loading panels repetition

In my website, in order to load diferent pages (to be multipage website) I have a main panel that has the id 'content-panel'.
When I want to load a diferent page I have a javascript function that is called 'loadPage' that loads the page (panel) that I want to the 'content-panel'.
But the page that I want to load has to have this code:
Ext.require(['*']);
Ext.onReady(function() {
...
var panel = Ext.Cmp('content-panel');
panel.add(loginPanel);
panel.layout.setActiveItem(loginPanel);
panel.doLayout();
panel.setLoading(false);
});
In this case it is loading the page/panel that is loginPanel, that is defined inside Ext.onReady
For me this is fine, I don't know of any other way of my website being multi-page.
But everytime that I want to go to a page it loads that page to the 'content-panel', even if it already been loaded before. I want a way to only add the page to 'content-panel' if it is not inside 'content-panel' items.
UPDATE:
Here is the loadPage
function swap(parent, replacement, url) {
var alpha = document.querySelector(parent);
var target = alpha.childNodes[0];
var omega = document.createElement(replacement);
omega.src = url;
omega.type = 'text/javascript';
alpha.replaceChild(omega, target);
}
function loadPage(panel, toPanel) {
toPanel.setLoading(true);
swap('head', 'script', panel);
}
it is used like this: loadPage('Ctl_base/view_admin/mainPage', Ext.getCmp('panel'));
I'm using CodeIgniter with ExtJS.
What I have already tried:
I want to do panel.add(loginPanel) only if the loginPanel doesn't exist.
I have tried:
if(panel.getComponent(loginScreen) == undefined) { panel.add(loginPanel); }
and it adds the component even when panel already has that component.
I have also tried:
function hasComponent(parent, child) {
parent.items.items.forEach(function(item) {
if(item == child){
return true;
}
});
return false;
}
if(!hasComponent(panel, loginPanel)) { panel.add(loginPanel); }
and it also doesn't work.
I have manage to tackle this question by putting and itemId on the panel that I want to load, and on panel.layout.setActiveItem(loginPanel); I have put panel.layout.setActiveItem('itemIdOfPanel');

Backbone.Marionette Change Region when Route changes

My app has a main region and some times there will be sub regions in the main region that should be accessible by an URL. The main region content is changed by a function the app router cause he knows the main region. But whats about the temporary regions in sub views?
So for example the url /docs will show a list of links to documents and /doc/:id should show the content of the doc beside the list. So how can /doc/:id could render the content when some one click on the list and render both list and content when some opens the url in a new tab for example.
As far as I can see there are two options having a router for every region, or the region manager fires events with route and region that should change. Any hints for the best way to solve this problem.
Ok I came up with a one router for every region solution. The router is easily configurable by a map of routes and views. When ever a route match the initially passed region will be shown a new instance of the view.
Here is an advanced version of the router where the route parameter will passed into the view.
Update
The solution above only works as long every route is registered only once. If you register the same route for the second time the callback for the first one will be overridden. So I came up with a solution where the region controller register a route not directly on the router but listen to an route:change event on a global eventbus (Marionette.Application.vent), and the router triggers an route:change event on this event bus.
RouterController:
// The problem with backbone router is that it can only register one function per route
// to overcome this problem every module can register routes on the RouterController
// the router will just trigger an event on the `app.vent` event bus when ever a registered routes match
define(function() {
function RouterController(vent) {
this.vent = vent;
this.router = new Backbone.Router();
}
RouterController.prototype = _.extend({
//just pass the route that change you wanna listen to
addRoutes: function(routes) {
_.each(routes, function(route) {
this.router.route(
route,
_.uniqueId('e'),
//create a closure of vent.trigger, so when ever the route match it simply trigger an event passing the route
// _.partial(_.bind(this.vent.trigger, this.vent), 'route:change', route)
_.bind(function() {
this.vent.trigger.apply(this.vent, ['route:change', route].concat(_.toArray(arguments)));
}, this)
);
}, this);
}
},
Backbone.Events);
return RouterController;
});
RegionRouter:
define(['common/App'], function(app) {
function RegionRouter(region, routerSettings) {
app.router.addRoutes(_.keys(routerSettings));
this.listenTo(app.vent, 'route:change', function() {
var route = arguments[0];
var View = routerSettings[route];
if (!View) {
return;
}
var params;
if (arguments.length > 1) {
params = computeParams(arguments, route);
}
region.show(new View(params));
});
}
RegionRouter.prototype = _.extend(
{
onClose: function() {
this.stopListening(app.vent);
}
}, Backbone.Events
);
function computeParams(args, route) {
args = Array.prototype.slice.call(args, 1);
//map the route params to the name in route so /someroute/:somevalue will become {somevalue: passedArgs}
//this object will be passed under the parameter key of the options
var params = {};
var regEx = /(?::)(\w+)/g;
var match = regEx.exec(route);
var count = 0;
while (match) {
params[match[1]] = args[count++];
match = regEx.exec(route);
}
return {params: params};
}
return RegionRouter;
}
);

Resources