I have a react component which shows a message:
Here's the code:
import React, { PropTypes } from 'react';
const Message = props => {
const { type, msg } = props;
if (type === 'success') {
return (<div>{msg}</div>);
} else {
return null;
}
};
Message.PropTypes = {
type: PropTypes.string.isRequired,
msg: PropTypes.string.isRequired,
};
export default Message;
//This component is called like this from index.js:
<Message type="success" msg="This is a Message" />
My question is...How can I call the component like I would call a function.
For example:
if (function is success then) {
//Show the Message Component
}
How can I do this with React?
If the if clause is within another React component you'd just render it,
class AnotherReact extends Component {
render() {
let alert = success ? <Message /> else '';
return (<div>{ alert }</div>);
}
}
Otherwise if not in a React component then you would have to use ReactDOM.render().
if (is success) {
ReactDOM.render(<Message />, document.querySelector());
}
Related
I'm new to reactjs, and I've just started using state management tool Mobx.
I'd like to show Snackbar using mobx store.
The logic is simple.
When the error was occured, it was added to mobx's observable alert list.
And, the observer component catched that change, and rebuild component.
Here is my code.
// BaseAlert.ts
interface BaseAlert {
message: string;
display: boolean;
}
export interface Success extends BaseAlert {
kind: 'success';
}
export interface Info extends BaseAlert {
kind: 'info';
}
export interface Warn extends BaseAlert {
kind: 'warn';
}
export interface Error extends BaseAlert {
kind: 'error';
}
export type Alert = Success | Info | Warn | Error;
export const alert = (message: string, kind: Alert['kind']): Alert =>
({
message,
kind,
display: true,
} as Alert);
// Alerts.ts
import {just, Maybe, nothing} from 'maybeasy';
import {action, computed, observable} from 'mobx';
import {Alert} from "../base/BaseAlert";
class AlertsStore {
#observable
alerts: Alert[] = [];
#computed
get current(): Maybe<Alert> {
return this.alerts.length === 0 ? nothing() : just(this.alerts[0]);
}
#action
hide = () => {
if (this.alerts.length > 0) {
this.alerts[0].display = false;
}
};
#action
process = () => {
this.alerts = this.alerts.slice(1);
};
#action
push = (alert: Alert) => {
this.hide();
this.alerts.push({...alert, display: true});
};
}
const alertsStore = new AlertsStore();
export default alertsStore;
// ObservableAlert.tsx
import React from 'react';
import alertsStore from "../../hook/AlertsStore";
import {Snackbar} from "#mui/material";
import {observer} from "mobx-react";
#observer
export class Alerts extends React.Component {
render() {
return alertsStore.current.cata({
Just: alert => (
<Snackbar
anchorOrigin={{vertical: 'top', horizontal: 'right'}}
open={alert.display}
onClose={alertsStore.hide}
onDragExit={alertsStore.process}
message={alert.message}
autoHideDuration={1000}
/>
),
Nothing: () => <></>,
});
}
}
And, I added the ObservableAlert component just below the App Component.
// App.tsx
function App() {
return (
<div className="App">
<Router>
<Routes>
<Route path="/*" element={<HomeView/>}>home</Route>
<Route path="/login" element={<LoginView/>}>login</Route>
</Routes>
</Router>
<Alerts/>
</div>
);
}
When I input username and password in textfield and click the login button, this function is called to push alert into the alertsStore.
async login(username: string, password: string) {
await this.baseEventHandle({
action: async () => {
await this.loginUserUseCase.invoke(username, password);
},
onError: (error) => {
alertsStore.push(alert(error, 'error'));
}
})
}
I checked above function's onError inner function was worked and new Alerts Instance was added to AlertsStore's Alert list.
But, the snackbar did not show at the browser.
Could you tell me what is the problem of my code??
I have two js files, including login and sidebar.
In the login.js(class component), it will call the loginAction to perform an API call and get back response data(id, role, username).
import LoginAction from auth.js
...
handleSubmit(event) {
event.preventDefault();
var response = LoginAction(this.state)
}
LoginAction is in the auth.js
export function LoginAction(data) {
const loginName = data.loginName;
const password = data.password;
var response = Login(loginName, password).then(response => {
if (response.data.errorCode === "") {
sessionStorage.setItem("token", response.data.data.token)
return {passwordError: null, loginNameError: null, isLoggedOn: true, role: response.data.data.isAdmin};
} else {
return formatError(response.data);
}
})
return response;
};
Here is the Login which is in the authservice.js
export const Login = (loginName, password) => {
const postData = { loginName, password }
console.log(postData);
return Axios.post("http://localhost:8080/login", postData, header);
}
how to pass the response to sidebar.js(class component)? I want to display the user's role on the sidebar.
if (data.isLoggedOn) {
console.log("routing to /employee")
this.props.router.navigate("/employee", { state: { "id": id, "role": role } })
}
I have tried to use the state to pass the data, but the role cannot be loaded to the sidebar after the first loading. The role only displays when I load the sidebar page again.
You can create a state in a common parent component and pass down a function that sets that state. Here's a quick example where I define a state user in the App and define login() which sets its state. Then I pass login() as a prop to the login button, and pass user as a prop to the sidebar.
CodeSandbox
import React, { Component } from "react";
export default class App extends Component {
constructor(){
super();
this.state = {user: null}
}
login(user) {
this.setState({user})
}
render() {
return (
<div className="App">
<Login loginHandler={(user)=>this.login(user)}/>
<Sidebar user={this.state.user}/>
</div>
);
}
}
class Sidebar extends Component{
render(){
return(
<div className="sidebar">Hey {this.props.user}</div>
)
}
}
class Login extends Component{
render(){
return(
<button onClick={()=>this.props.loginHandler('Foo')}>Login</button>
)
}
}
I am new to react and I'm trying to print out some data from a GET request and print them out but I'm getting Uncaught TypeError: this.props.todos.map is not a function - error.
If I change this.setState({data: data}) on the success function I can get the data in my console, but is there any quick and simple way to fix this to print the data?
App.js
import React, { Component } from 'react';
import $ from 'jquery';
import Todos from "./Components/Todos"
class App extends Component {
constructor(){
super();
this.state = {
todos:[]
}
}
getTodos(){
$.ajax({
type: "GET",
url: 'url',
dataType:'json',
headers: {
Authorization: "Basic " + btoa("username" + ":" + "password")
},
cache: false,
success: function(data){
this.setState({todos: data}, function(){
console.log(this.state);
});
}.bind(this),
error: function(xhr, status, err){
console.log(err);
}
});
}
componentWillMount(){
this.getTodos();
}
componentDidMount(){
this.getTodos();
}
render() {
return (
<div className="App">
<Todos todos={this.state.todos}/>
</div>
);
}
}
export default App;
Todos.js
import React, { Component } from 'react';
import TodoItem from './TodoItem';
class Todos extends Component {
render() {
let todoItems;
if(this.props.todos){
todoItems = this.props.todos.map(todo => {
return (
<TodoItem key={todo.code} todo={todo} />
);
});
}
return (
<div className="Todos">
<h3>Results:</h3>
{todoItems}
</div>
);
}
}
export default Todos;
TodoItem.js
import React, { Component } from 'react';
class TodoItem extends Component {
render() {
return (
<li className="Todo">
<strong>{this.props.todo.code}</strong>
</li>
);
}
}
export default TodoItem;
Snippet from the console log:
todos has to be an array always. So change setState like this in your success function.
this.setState({todos: (data.resources ? data.resources : [])}, function(){
console.log(this.state);
});
You also need to bind the getTodos function to reference the scope of the class inside it. So bind it inside constructor :
this.getTodos = this.getTodos.bind(this)
First thing it useless to call this.getTodos() twice just call it in the componentDidMount() methode, one more thing that you need to be aware of , the component TodoItem.js will render no mather the value of this.props.todo.code to avoid the issue you need added this into your TodoItem.js
class TodoItem extends Component {
render() {
if(!this.props.todo){
<div>Loading</div>
}
return (
<li className="Todo">
<strong>{this.props.todo.code}</strong>
</li>
);
}
}
the Array.prototype.map() expect an array so if you try to pass an undefined value to it , it will automatically trow an error wish i believe is the case in your app
one more thing make sur that this.state.todo before trying my solution
Here is my code:
ChartActions.js
import * as types from './ChartTypes.js';
export function chartData(check){
return { type: types.CHART_DATA,check };
}
ChartTypes.js
export const CHART_DATA = 'CHART_DATA';
ChartReducers.js
import {
CHART_DATA,
}from './ChartTypes.js';
const initialState = {
chartData : [],
}
export default function ChartReducers(state = initialState, action) {
switch (action.type) {
case CHART_DATA :
return Object.assign({}, state, {
chartData : action.check
});
default:
return state;
}
}
I am so sure that I setup redux quite accurate and it works perfectly. My problem is:
In a component A I dispatch a function:
handleClick(){
this.props.ChartActions.chartData("test string")
}
so in theory, a component B in my project will receive the string "test string" right after the handeClick function triggered, like this
componentWillReceiveProps(){
console.log(this.props.chartData) // test string
}
But I have no idea why SOMETIMES (it only happens sometimes) I have to trigger handleClick function TWO times in component A so that the component B could be able to get the updated state (in this example, it is "test string"). I supposed it's a bug.
I need the component B will receive the updated state (i.e "test string") RIGHT AFTER the handleClick is triggered only ONE TIME.
I have a container:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as ChartActions from '../../components/center-menu/services/ChartActions.js';
import CenterMenu from '../../components/center-menu/center-menu-index.js'
import RightMenu from '../../components/right-content/right-content-index.js'
class Home extends Component {
render() {
return (
<div>
<CenterMenu
ChartActions = {this.props.ChartActions}
/>
<RightMenu
ChartProps={this.props.ChartProps}
/>
</div>
);
}
}
function mapStateToProps(state) {
return {
ChartProps: state.ChartReducers
};
}
function mapDispatchToProps(dispatch) {
return {
ChartActions: bindActionCreators(ChartActions, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Home);
Here is the component A where I fire an actions:
import React, { Component } from 'react';
class CenterMenu extends Component {
constructor(props) {
super(props);
constructor(props) {
super(props);
this.state = {};
}
}
handleClick(){
this.props.ChartActions.chartData('test string')
}
render() {
return (
<div className="center_menu" onClick={this.handleClick.bind(this)}>
Some stuff
</div>
)
}
}
export default CenterMenu;
And in another component B:
import React, { Component } from 'react';
class RightMenu extends Component {
constructor(props) {
super(props);
constructor(props) {
super(props);
this.state = {};
}
}
componentWillReceiveProps(){
console.log(this.props.ChartProps.chartData, "right here")
}
render() {
return (
<div className="center_menu">
Some stuff
</div>
)
}
}
export default RightMenu;
Weird thing:
In Component A, if I trigger the handleClick function by clicking in a div tag, it fires an action that change the initial state to "test string"
But...
In the component B the statement
console.log(this.props.ChartProps.chartData, "right here")
show empty string first like this:
right here
But when I trigger the handleClick function the SECOND TIME in component A , then in component B, in the statement
console.log(this.props.ChartProps.chartData, "right here")
it show the following:
test string "right here"
which is the result I want to achieve.
But I don't understand why I HAVE TO trigger the handleClick function twice. I need it by one click.
The problem is your Home component doesn't rerender the children. Try keeping ChartProps in a state in Home like so:
class Home extends Component {
state = {
ChartProps: null //you can use some default value, this might cause undefined is not an object error in you children
}
componentDidMount() {
const { ChartProps } = this.props
this.setState(() => ({ ChartProps }))
}
componentWillReceiveProps() {
const { ChartProps } = this.props
this.setState(() => ({ ChartProps }))
}
render() {
const { ChartProps } = this.state
return (
<div>
<CenterMenu
ChartActions={this.props.ChartActions}
/>
<RightMenu
ChartProps={ChartProps}
/>
</div>
);
}
}
function mapStateToProps(state) {
return {
ChartProps: state.ChartReducers
};
}
function mapDispatchToProps(dispatch) {
return {
ChartActions: bindActionCreators(ChartActions, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Home);
I'm using react 15.3.1, with react-router 2.4.1 and react-router-redux 4.0.5:
When I trap the routing change with:
this.props.router.setRouteLeaveHook(
this.props.route,
this.routerWillLeave
);
private routerWillLeave = () => {
if (this.state.editing)
return 'You may have unsaved changes. Are you sure you want to leave?'
};
... I do get my this.routerWillLeave method called, but the URL in the browser still changes, so even if the user stays on the page by deciding not to leave the page, the URL is now wrong. Ideas?
export default class extends Component {
static contextTypes = {
router: React.PropTypes.object.isRequired
}
state = {
editing: true
}
componentDidMount() {
this.context.router.setRouteLeaveHook(this.props.route, () => {
if (this.state.editing) {
return false;
// At here you can give a confirm dialog, return true when confirm true
}else {
return true;
}
})
}
}
And if your react-route >2.4, you can also use withRouter to wrap your component, this's may be better!
import React, {Component} from 'react';
import {render} from 'react-dom';
import {withRouter} from 'react-router';
export default withRouter(class extends Component {
state = {
unsaved: true
}
componentDidMount() {
this.props.router.setRouteLeaveHook(this.props.route, () => {
if (this.state.unsaved) {
return false;
// At here you can give a confirm dialog, return true when confirm true
}else {
return true;
}
})
}
render() {
return (
<div>
<h2>About</h2>
{this.props.children || "This is outbox default!"}
</div>
)
}
})