How to refresh a List View in admin on rest - reactjs

I am trying to get a list to refresh after a custom action was successfully executed.
i used the saga from the admin on rest tutorial
function * actionApproveSuccess () {
yield put(showNotification('Executed'))
yield put(push('/comments'))
// does not refresh, because the route does not change
// react-redux-router also has no refresh() method, like react-router has...
}
the other idea i had was to somehow trigger the refresh action of the list component, but i have no idea how to access that or how to hook that up to the ACTION_SUCCESS event.

There is no way to refresh a route via react router, and that's a known problem. Admin-on-rest's List component has its own refresh mechanism, but offers no API for it.
My advice would be to use a custom <List> component based on admin-on-rest's one. And if you find a way to expose the refresh action, feel free to open a PR on the aor repository!

#Danila Smirnov's answer above shows this message when I use it now:
Deprecation warning: The preferred way to refresh the List view is to connect your custom button with redux and dispatch the refreshView action.
Clicking the refresh button itself wasn't working either nowadays.
Here's the tweaked version that I got working in mine.
Edit: Modified it a bit more to make it reusable.
RefreshListActions.js
import React, { Component } from 'react'
import FlatButton from 'material-ui/FlatButton'
import { CardActions } from 'material-ui/Card'
import NavigationRefresh from 'material-ui/svg-icons/navigation/refresh'
import { connect } from 'react-redux'
import { REFRESH_VIEW } from 'admin-on-rest/src/actions/uiActions'
import { refreshView as refreshViewAction } from 'admin-on-rest/src/actions/uiActions'
class MyRefresh extends Component {
componentDidMount() {
const { refreshInterval, refreshView } = this.props
if (refreshInterval) {
this.interval = setInterval(() => {
refreshView()
}, refreshInterval)
}
}
componentWillUnmount() {
clearInterval(this.interval)
}
render() {
const { label, refreshView, icon } = this.props;
return (
<FlatButton
primary
label={label}
onClick={refreshView}
icon={icon}
/>
);
}
}
const RefreshButton = connect(null, { refreshView: refreshViewAction })(MyRefresh)
const RefreshListActions = ({ resource, filters, displayedFilters, filterValues, basePath, showFilter, refreshInterval }) => (
<CardActions>
{filters && React.cloneElement(filters, { resource, showFilter, displayedFilters, filterValues, context: 'button' }) }
<RefreshButton primary label="Refresh" refreshInterval={refreshInterval} icon={<NavigationRefresh />} />
</CardActions>
);
export default RefreshListActions
In my list that I want to refresh so often:
import RefreshListActions from './RefreshListActions'
export default (props) => (
<List {...props}
actions={<RefreshListActions refreshInterval="10000" />}
>
<Datagrid>
...
</Datagrid>
</List>
)

Definitely hacky, but a work-around could be:
push('/comments/1') //any path to change the current route
push('/comments') //the path to refresh, which is now a new route

using refreshView action via redux works well.
see example....
import { refreshView as refreshViewAction } from 'admin-on-rest';
import { connect } from 'react-redux';
class MyReactComponent extends Component {
//... etc etc standard react stuff...
doSomething() {
// etc etc do smt then trigger refreshView like below
this.props.refreshView();
}
render() {
return <div>etc etc your stuff</div>
}
}
export default connect(undefined, { refreshView: refreshViewAction })(
MyReactComponent
);

I've solve this task with small hack via Actions panel. I'm sure it is not correct solution, but in some situations it can help:
class RefreshButton extends FlatButton {
componentDidMount() {
if (this.props.refreshInterval) {
this.interval = setInterval(() => {
this.props.refresh(new Event('refresh'))
}, this.props.refreshInterval)
}
}
componentWillUnmount() {
clearInterval(this.interval)
}
}
const StreamActions = ({ resource, filters, displayedFilters, filterValues, basePath, showFilter, refresh }) => (
<CardActions>
{filters && React.cloneElement(filters, { resource, showFilter, displayedFilters, filterValues, context: 'button' }) }
<RefreshButton primary label="Refresh streams" onClick={refresh} refreshInterval={15000} refresh={refresh} icon={<NavigationRefresh />} />
</CardActions>
);
export default class StreamsListPage extends Component {
render() {
return (
<List
{...this.props}
perPage={20}
actions={<StreamActions />}
filter={{ active: true }}
title='Active Streams'>
<StreamsList />
</List>
)
}
}

The push is just a redirect for AOR which did not seem to work for me either. What guleryuz posted was on the right track for me.. Here's what I did building on his example:
// Import Statement
import { refreshView as refreshViewAction } from 'admin-on-rest';
class RemoveButton extends Component {
handleClick = () => {
const { refreshView, record, showNotification } = this.props;
fetch(`http://localhost:33333/api/v1/batch/stage/${record.id}`, { method: 'DELETE' })
.then(() => {
showNotification('Removed domain from current stage');
refreshView();
})
.catch((e) => {
console.error(e);
showNotification('Error: could not find domain');
});
}
render() {
return <FlatButton secondary label="Delete" icon={<DeleteIcon />}onClick={this.handleClick} />;
}
}
These bits are important as well:
RemoveButton.propTypes = {
record: PropTypes.object,
showNotification: PropTypes.func,
refreshView: PropTypes.func,
};
export default connect(null, {
showNotification: showNotificationAction,
refreshView: refreshViewAction,
})(RemoveButton);
So the way this works is it uses AOR's refreshViewAction as a prop function. This uses the underlying call to populate the data grid for me which is GET_LIST. This may not apply to your specific use case. Let me know if you have any questions.

Pim Schaaf's solution worked like a charm for me, Mine looks a bit different
yield put(push('/comments/-1')); // This refreshes the data
yield put(showNotification('')); // Hide error

Related

I wonder if this really is the correct way to use onAuthStateChanged

Following this react-firestore-tutorial
and the GitHub code. I wonder if the following is correct way to use the onAuthStateChanged or if I have understod this incorrect I'm just confused if this is the right way.
CodeSandBox fully connect with a test-account with apikey to Firebase!! so you can try it what I mean and I can learn this.
(NOTE: Firebase is blocking Codesandbox url even it's in Authorised domains, sorry about that but you can still see the code)
t {code: "auth/too-many-requests", message: "We have blocked all
requests from this device due to unusual activity. Try again later.",
a: null}a:
Note this is a Reactjs-Vanilla fully fledge advanced website using only;
React 16.6
React Router 5
Firebase 7
Here in the code the Firebase.js have this onAuthStateChanged and its called from two different components and also multiple times and what I understand one should only set it up once and then listen for it's callback. Calling it multiple times will that not create many listeners?
Can someone have a look at this code is this normal in Reactjs to handle onAuthStateChanged?
(src\components\Firebase\firebase.js)
import app from 'firebase/app';
import 'firebase/auth';
import 'firebase/firestore';
class Firebase {
constructor() {
app.initializeApp(config);
.......
}
.....
onAuthUserListener = (next, fallback) =>
this.auth.onAuthStateChanged(authUser => {
if (authUser) {
this.user(authUser.uid)
.get()
.then(snapshot => {
const dbUser = snapshot.data();
// default empty roles
if (!dbUser.roles) {
dbUser.roles = {};
}
// merge auth and db user
authUser = {
uid: authUser.uid,
email: authUser.email,
emailVerified: authUser.emailVerified,
providerData: authUser.providerData,
...dbUser,
};
next(authUser);
});
} else {
fallback();
}
});
user = uid => this.db.doc(`users/${uid}`);
}
export default Firebase;
This two rect-higher-order Components:
First withAuthentication:
(src\components\Session\withAuthentication.js)
import React from 'react';
import AuthUserContext from './context';
import { withFirebase } from '../Firebase';
const withAuthentication = Component => {
class WithAuthentication extends React.Component {
constructor(props) {
super(props);
this.state = {
authUser: JSON.parse(localStorage.getItem('authUser')),
};
}
componentDidMount() {
this.listener = this.props.firebase.onAuthUserListener(
authUser => {
localStorage.setItem('authUser', JSON.stringify(authUser));
this.setState({ authUser });
},
() => {
localStorage.removeItem('authUser');
this.setState({ authUser: null });
},
);
}
componentWillUnmount() {
this.listener();
}
render() {
return (
<AuthUserContext.Provider value={this.state.authUser}>
<Component {...this.props} />
</AuthUserContext.Provider>
);
}
}
return withFirebase(WithAuthentication);
};
export default withAuthentication;
And withAuthorization:
(src\components\Session\withAuthorization.js)
import React from 'react';
import { withRouter } from 'react-router-dom';
import { compose } from 'recompose';
import AuthUserContext from './context';
import { withFirebase } from '../Firebase';
import * as ROUTES from '../../constants/routes';
const withAuthorization = condition => Component => {
class WithAuthorization extends React.Component {
componentDidMount() {
this.listener = this.props.firebase.onAuthUserListener(
authUser => {
if (!condition(authUser)) {
this.props.history.push(ROUTES.SIGN_IN);
}
},
() => this.props.history.push(ROUTES.SIGN_IN),
);
}
componentWillUnmount() {
this.listener();
}
render() {
return (
<AuthUserContext.Consumer>
{authUser =>
condition(authUser) ? <Component {...this.props} /> : null
}
</AuthUserContext.Consumer>
);
}
}
return compose(
withRouter,
withFirebase,
)(WithAuthorization);
};
export default withAuthorization;
This is normal. onAuthStateChanged receives an observer function to which a user object is passed if sign-in is successful, else not.
Author has wrapped onAuthStateChanged with a higher order function – onAuthUserListener. The HOF receives two parameters as functions, next and fallback. These two parameters are the sole difference when creating HOC's withAuthentication and withAuthorization.
The former's next parameter is a function which stores user data on localStorage
localStorage.setItem('authUser', JSON.stringify(authUser));
this.setState({ authUser });
while the latter's next parameter redirects to a new route based on condition.
if (!condition(authUser)) {
this.props.history.push(ROUTES.SIGN_IN);
}
So, we are just passing different observer function based on different requirements. The component's we will be wrapping our HOC with will get their respective observer function on instantiation. The observer function are serving different functionality based on the auth state change event. Hence, to answer your question, it's completely valid.
Reference:
https://firebase.google.com/docs/reference/js/firebase.auth.Auth#onauthstatechanged
https://reactjs.org/docs/higher-order-components.html

How can i use react-toastify from hook?

I found useToast and useToastContainer, but documentation is absent and i don't understand how tu use these hooks. Can anyone provide some info about these hooks?
The toasts inherit ToastContainer’s props. Props defined on toast supersede ToastContainer’s props.
There are two ways you can use toasts in your application:
1. Define ToastContainer inside the component
import { ToastContainer, toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
const App = () => {
notify = () => toast("Wow so easy !");
return (
<div>
<button onClick={notify}>Notify !</button>
// You can add <ToastContainer /> in root component as well.
<ToastContainer />
</div>
);
}
2. Call toast.configure() once in your app. At the root of your app is the best place.
The library will mount a ToastContainer for you if none is mounted.
import { toast } from "react-toastify";
import 'react-toastify/dist/ReactToastify.css';
// Call it once in your app. At the root of your app is the best place
toast.configure()
const App = () => {
notify = () => toast("Wow so easy !");
return (
<button onClick={notify}>Notify !</button>
);
}
You can use either of them. I prefer the 2nd method because you only need to define toast.configure() which is quite clean way to add it.
You can add configuration as per your need like below:
toast.configure({
autoClose: 8000,
draggable: false,
//etc you get the idea
});
EDIT
If you want to use toast hooks, then you must wrap your app with the ToastProvider to have access to its context elsewhere within your app.
import { ToastProvider, useToasts } from 'react-toast-notifications'
const FormWithToasts = () => {
const { addToast } = useToasts()
const onSubmit = async value => {
const { error } = await dataPersistenceLayer(value)
if (error) {
addToast(error.message, { appearance: 'error' })
} else {
addToast('Saved Successfully', { appearance: 'success' })
}
}
return <form onSubmit={onSubmit}>...</form>
}
const App = () => (
<ToastProvider>
<FormWithToasts />
</ToastProvider>
)

React Native - Create method in functional component and call this method outside of the component, possible?

I have a custom modal component:
export default ModalLoader = props=>{
const {
loading,
...attributes
} = props;
function closeModal(){
console.log('Close Modal Kepanggil coyyy!!!!!!!')
}
return(
<Modal
transparent={true}
animationType={'none'}
visible={loading}
>
<View>
<View>
<ActivityIndicator
size={'large'}
color={colors.darkRed}
/>
<CustomText style={{fontSize:24}}>Mohon Tunggu...</CustomText>
</View>
</View>
</Modal>
)
}
i want to used closeModal() in axios instance, so everytime axios get a response, i want to close modal in axios file itself not in all of my component,
let say my axios instance something like this:
AxiosHttp.interceptors.response.use((res)=>{
CustomLog(res.data, 'Interceptor')
// call closeModal of ModalLoader
ModalLoader.closeModal()
return res.data;
},(err)=>{
CustomLog(err, 'Interceptor Error')
// call closeModal of ModalLoader
ModalLoader.closeModal()
return Promise.reject(err)
})
export default AxiosHttp
is it possible to do that?
One way is to use React Context.
Create a context provider with the function you want to use to close/toggle the modal. Then in the ModalLoader (or any component of choice) use the function from that context.
./ModalContext.jsx
import React, { createContext } from 'react';
const ModalContext = createContext({
closeModal: () => {
console.log('Close Modal Kepanggil coyyy!!!!!!!');
},
});
export default ModalContext;
With the introduction of react-hooks in v16.8.0 you can use context in functional components using the useContext hook.
Axios instance
import React, { useContext } from 'react';
import { ModalContext } from './ModalContext';
const modalContext = useContext(ModalContext);
AxiosHttp.interceptors.response.use((res)=>{
CustomLog(res.data, 'Interceptor')
// call closeModal in context
modalContext.closeModal()
return res.data;
},(err)=>{
CustomLog(err, 'Interceptor Error')
// call closeModal in context
modalContext.closeModal()
return Promise.reject(err)
})
export default AxiosHttp;
See working example to play around with here: https://codepen.io/studiospindle/pen/xxxMRow
In that example there is a async function as an example which will close the modal window after three seconds. This is to mimic the Axios example. Also provided an example with a button.
a simple example on using react context as #Remi suggested
Core part is ModalContext.js. It exports the context for other components.
You can edit the state inside the provider if you need more common function/prop.
If you really need a static function to do so. You might need a manager
class ModalInstanceManager {
_defaultInstance = null;
register(_ref) {
if (!this._defaultInstance && "_id" in _ref) {
this._defaultInstance = _ref;
}
}
unregister(_ref) {
if (!!this._defaultInstance && this._defaultInstance._id === _ref._id) {
this._defaultInstance = null;
}
}
getDefault() {
return this._defaultInstance;
}
}
export default new ModalInstanceManager();
In your ModalLoader:
componentDidMount() {
ModalInstanceManager.register(this);
}
then in your static function:
ModalLoader.open/close = ()=> {
ModalInstanceManager.getDefault().open/close();
}

Render HOC(Component) without changing Component Name in JSX

I have two HOCs that add context to a component like so :
const withContextOne = Component => class extends React.Component {
render() {
return (
<ContextOne.Consumer>
{context => <Component {...this.props} one={context} /> }
</ContextOne.Consumer>
);
}
};
export default withContextOne;
Desired Result
I just want an syntactically concise way to wrap a component with this HOC so that it doesn't impact my JSX structure too much.
What I have tried
Exporting a component with the HOC attached export default withContextOne(withContextTwo(MyComponent)) This way is the most concise, but unfortunately it breaks my unit tests.
Trying to evaluate the HOC from within JSX like :
{ withContextOne(withContextTwo(<Component />)) }
This throws me an error saying
Functions are not valid as a React child. This may happen if you return a Component instead of < Component /> from render.
Creating a variable to store the HOC component in before rendering :
const HOC = withContextOne(Component)
Then simply rendering with <HOC {...props}/> etc. I don't like this method as it changes the name of the component within my JSX
You can set the displayName before returning the wrapped component.
const withContextOne = Component => {
class WithContextOneHOC extends React.Component {
render() {
return (
<ContextOne.Consumer>
{context => <Component {...this.props} one={context} /> }
</ContextOne.Consumer>
);
}
}
WithContextOneHOC.displayName = `WithContextOneHOC(${Component.displayName})`;
return WithContextOneHOC;
};
This will put <WithContextOneHOC(YourComponentHere)> in your React tree instead of just the generic React <Component> element.
You can use decorators to ease the syntactic pain of chained HOCs. I forget which specific babel plugin you need, it might (still) be babel-plugin-transform-decorators-legacy or could be babel-plugin-transform-decorators, depending on your version of babel.
For example:
import React, { Component } from 'react';
import { withRouter } from 'react-router';
import { injectIntl } from 'react-intl';
import { connect } from 'react-redux';
import { resizeOnScroll } from './Resize';
#withRouter
#resizeOnScroll
#injectIntl
#connect(s => s, (dispatch) => ({ dispatch }))
export default class FooBar extends Component {
handleOnClick = () => {
this.props.dispatch({ type: 'LOGIN' }).then(() => {
this.props.history.push('/login');
});
}
render() {
return <button onClick={}>
{this.props.formatMessage({ id: 'some-translation' })}
</button>
}
}
However, the caveat with decorators is that testing becomes a pain. You can't use decorators with const, so if you want to export a "clean" undecorated class you're out of luck. This is what I usually do now, purely for the sake of testing:
import React, { Component } from 'react';
import { withRouter } from 'react-router';
import { injectIntl } from 'react-intl';
import { connect } from 'react-redux';
import { resizeOnScroll } from './Resize';
export class FooBarUndecorated extends Component {
handleOnClick = () => {
this.props.dispatch({ type: 'LOGIN' }).then(() => {
this.props.history.push('/login');
});
}
render() {
return <button onClick={}>
{this.props.formatMessage({ id: 'some-translation' })}
</button>
}
}
export default withRouter(
resizeOnScroll(
injectIntl(
connect(s => s, ({ dispatch }) => ({ dispatch }))(
FooBarUndecorated
)
)
)
);
// somewhere in my app
import FooBar from './FooBar';
// in a test so I don't have to use .dive().dive().dive().dive()
import { FooBarUndecorated } from 'src/components/FooBar';

Where to load data in a React/Redux/Routerv4 app?

I am working on an app that functions almost entirely on the following route: /task/:taskId and there is nothing to render at the / route. Instead when you launch the app it should redirect to a task. Much like when you open slack and you are in a channel. Not on some page that says please select a channel. I cannot figure out where I'm suppose to load the tasks from the database.
Right now, I have a component that loads at / and in its componentDidMount method, I load the tasks from the database. Once the tasks are loaded, I do a history.push to redirect to the first task in the array of tasks from the database.
This all works great until I get redirected to a specific task and then refresh the page. That is, if I'm at /task/foobar and I refresh the page, the app doesn't load anything because tasks is only loaded from the database at /.
So what is the proper way to make sure that my data is loaded no matter which page I'm on?
EDIT - Adding some code for reference:
/routes.js - / uses Master.js and /task/:taskId uses TaskPage.js
import React from 'react';
import { Switch, Route } from 'react-router';
import App from './containers/App';
import Master from './containers/Master';
import TaskPage from './containers/TaskPage';
export default () => (
<App>
<Switch>
<Route exact path="/" component={Master} />
<Route path="/task/:taskId" component={TaskPage} />
</Switch>
</App>
);
/containers/Master.js - loadTasks is action creator that passes tasks data to tasks reducer. Also returns promise that resolves to the tasks data once loaded from local nedb database.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { loadTasks } from '../actions/tasks';
class Master extends Component {
componentDidMount() {
this.fetchData();
}
fetchData() {
this.props.loadTasks()
.then(tasks => {
const id = tasks[Object.keys(tasks)[0]]._id;
this.props.history.push(`/task/${id}`);
return true;
})
.catch(e => {
console.log('ERROR', e);
});
}
render() {
return (
<div>
{this.props.children}
</div>
);
}
}
const mapStateToProps = (props, ownProps) => ({
taskId: ownProps.taskId
});
export default connect(mapStateToProps, {
loadTasks
})(Master);
/containers/TaskPage.js - Not much to see here. Just passing props to wrapped component Task which renders just fine as long as the app loads / first. If you hit /task/:taskId directly, nothing loads because the tasks data in the database only gets loaded to the redux store at /
import { connect } from 'react-redux';
import Task from '../components/Task';
const mapStateToProps = ({ tasks }, { match: { params: { taskId } } }) => ({
tasks,
taskId
});
export default connect(mapStateToProps)(Task);
Edit - I have removed my old answers in favor of what I think is a better approach based on some digging I've done.
I've seen a few things from Dan Abramov in a few places that make me think he'd do the data fetching inside of the component (not container) and he would use the componentDidMount lifecycle method to do so. The only piece I am not certain about is how best to decide if the app should display a loading status.
In Dan's and others' examples, they just check to see if state.loading is false and if state.items.length is 0. This only works if you always expect items to have data. But in my case, when you first launch the app, you will have no data in it so checking for 0 length doesn't really work. Instead I've added an initLoad property to the state.
I'm open to other ways of handling this if there are better methods.
routes.js
import React from 'react';
import { Switch, Route } from 'react-router';
import App from './containers/App';
import TaskPage from './containers/TaskPage';
export default () => (
<App>
<Switch>
<Route exact path="/" component={TaskPage} />
<Route path="/task/:taskId" component={TaskPage} />
</Switch>
</App>
);
TaskPage.js
import { connect } from 'react-redux';
import { withRouter } from 'react-router';
import { loadTasks } from '../actions/tasks';
import Task from '../components/Task';
const mapStateToProps = ({ tasks }, { match: { params: { taskId } } }) => ({
tasks,
taskId,
});
const mapDispatchToProps = dispatch => ({
loadTasks: () => dispatch(loadTasks())
});
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Task));
Task.js
import React, { Component } from 'react';
import type { tasksType } from '../reducers/tasks';
import SidebarContainer from '../containers/SidebarContainer';
class Task extends Component {
props: {
loadTasks: () => any,
initForm: () => any,
taskId: string,
tasks: tasksType,
history: any
};
componentDidMount() {
this.fetchData();
}
fetchData() {
this.props.loadTasks()
.then(tasks => {
const taskId = this.props.taskId;
if (!taskId) {
const id = tasks[Object.keys(tasks)[0]]._id;
this.props.history.push(`/task/${id}`);
}
return true;
})
.catch(e => {
console.log('ERROR', e);
});
}
render() {
const { tasks, taskId, initForm } = this.props;
if (!tasks.initLoad || (tasks.loading && Object.keys(tasks.items).length < 1)) {
return <div>Loading</div>;
}
return (
<div id="wrapper">
<SidebarContainer taskId={taskId} />
<div id="main">
<div id="group-details">
{tasks.initLoad && Object.keys(tasks.items).length > 0 ?
(<div>Task details would go here...</div>) :
(<div>Please create a task</div>)
}
</div>
</div>
</div>
);
}
}
export default Task;

Resources