I am transferring my CRA to a Nextjs and I am having a bit of an issue with anything that uses the <canvas> element. The charts and data are mostly there, but my annotations are now missing from the charts. I have tried importing everything with the dynamic function for the parent element, but it still seems to not show the missing features.
I am also seeing some weird things happening on an arcgis map which is not visualizing 3d elements on a <canvas>. So my guess is that this has something with the way that canvas or d3 interact with the browser.
// parent component
import moment from 'moment-timezone';
import React, { useRef } from 'react';
import {
Chart as ChartJS,
LinearScale,
CategoryScale,
BarElement,
PointElement,
LineElement,
Legend,
Tooltip,
} from 'chart.js';
// import { Chart } from 'react-chartjs-2';
import { ArrowRight } from '../../icons/ArrowRight';
import Link from 'next/link';
import { chartOptions } from '../../lib/chartOptions';
import dynamic from 'next/dynamic';
const Chart = dynamic((): any => import('react-chartjs-2').then((m: any) => m.Chart), {
ssr: false,
});
ChartJS.register(
LinearScale,
CategoryScale,
BarElement,
PointElement,
LineElement,
Legend,
Tooltip,
);
export const MarkupCard = ({ item }: any) => {
const chartRef = useRef();
const userName = item.user_id.split('#')[0];
return (
<div className="w-2/3 pb-10 mx-auto border-2 border-blue items-center rounded-lg my-4 py-4 flex flex-row justify-between">
<div className="w-full text-left pl-4 pb-6 h-72">
<div className="w-full flex flex-row justify-between">
<h2 className="text-lg font-bold">{userName} Marked up a chart</h2>
<div className=" w-1/3 text-right pr-4">
<h2>
{moment(item.created_at)
.tz(process.env.NEXT_PUBLIC_TIMEZONE ?? '')
.format('MM-DD-YYYY hh:mm:ss a')}
</h2>
</div>
</div>
<h2>Route: {item.routeLongName}</h2>
<Chart
style={{ height: '100px', width: '99%' }}
ref={chartRef}
plugins={item.details.options.plugins}
className="trips-chart"
type="line"
options={chartOptions(item.details.options, item.details.annotations)}
data={item.details.chartData}
/>
</div>
<Link href={`/app/markupDetail/${item.id}`}>
<button className="mx-6 h-full flex">
<ArrowRight />
</button>
</Link>
</div>
);
};
// chart component
import React, { useEffect, useState } from 'react';
import { useDispatch } from 'react-redux';
import { supabase } from '../../client';
import { actions } from '../../store';
import { SocialNote } from '../../types';
import { Card } from './Card';
const SocialFeed = () => {
const [feed, setFeed] = useState<SocialNote[]>([]);
const dispatch = useDispatch();
const loadPage = async () => {
dispatch(actions.setLoaded(true));
const { data, error } = await supabase
.from('notes')
.select('*')
.order('last_update', { ascending: false });
if (data) {
setFeed(data);
console.log(data);
return data;
} else {
return error;
}
};
useEffect((): (() => void) => {
loadPage();
return () => supabase.removeAllSubscriptions();
}, []);
return (
<div className="w-full mx-auto overflow-y-auto">
{feed.map((item, key) => (
<Card key={key} item={item} />
))}
</div>
);
};
export default SocialFeed;
// chartoptions.js
export const chartOptions: any = (options: any, annotations: any) => {
const { title } = options;
const { tooltip } = options.plugins;
return {
title,
responsive: true,
maintainAspectRatio: false,
interaction: {
mode: 'index' as const,
intersect: false,
},
plugins: {
annotation: { annotations: annotations },
tooltip,
legend: {
position: 'top' as const,
},
title,
},
};
};
// next.config.js
/** #type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
}
module.exports = nextConfig
CRA
Nextjs
Turns out I just needed to register Annotations in ChartJS.register()
This was not the case for React, but was required for Nextjs
import Annotation from 'chartjs-plugin-annotation';
ChartJS.register(
Annotation,
LinearScale,
CategoryScale,
BarElement,
PointElement,
LineElement,
Legend,
Tooltip,
);
Related
I made an application in which the user passes coordinates. The function makes a request to the server according to the given coordinates and looks for the nearest available establishments. Further, the data is transferred to the formatter and finally to the state. This is what App.tsx looks like
//App.tsx
import React, { useEffect, useState } from "react";
import "./App.css";
import { useAppSelector } from "./hook";
import { useRequestPlaces } from "./hooks/index";
import { useAppDispatch } from "./hook";
const cities = [
{ name: "New York", latlong: "40.760898,-73.961219" },
{ name: "London", latlong: "51.522479,-0.104528" },
{ name: "London Suburb", latlong: "51.353340,-0.032366" },
{ name: "Desert", latlong: "22.941602,25.529665" },
];
const defaultLatlong = "40.760898,-73.961219";
function App() {
const dispatch = useAppDispatch();
const fetchPlaces = useRequestPlaces();
const { ids, entities } = useAppSelector((state) => state.places);
const [latlong, setLatlong] = useState(defaultLatlong);
const minRadius = 50;
useEffect(() => {
fetchPlaces(minRadius, latlong, dispatch);
console.log(entities);
}, [fetchPlaces, latlong, entities, ids]);
return (
<div className="App">
<header className="App-header">
{cities.map((city) => {
return (
<button
type="button"
className="btn btn-outline-light"
onClick={() => {
setLatlong(city.latlong);
console.log(latlong);
}}
>
{city.name}
</button>
);
})}
</header>
<main>
{ids.map((id, index) => {
const place = entities[id];
return (
<div
className="card mx-auto mt-2"
key={index}
style={{ width: "18rem" }}
>
<div className="card-body">
<h5 className="card-title">{place?.name}</h5>
<h6 className="card-subtitle mb-2 text-muted">
<ul>
{place?.categories.map((category) => {
return <li key={category.id}>{category.name}</li>;
})}
</ul>
</h6>
<p className="card-text">
Distance: {place?.distance} meters
<br />
Adress: {place?.location}
</p>
</div>
</div>
);
})}
</main>
</div>
);
}
export default App;
At this stage, the user transmits the coordinates by clicking on the buttons with cities. Next, the coordinates are passed to the API handler functions.
//fetch.ts
import { Dispatch } from "react";
import { getClosestPlaces } from "./getClosestPlaces";
import { placesActions } from "../../slices";
import { Action } from "redux";
import client from "./client";
const fetch = async (
radius: number,
latlong: string,
dispatch: Dispatch<Action>
) => {
const { fetchPlaces } = client();
const params = {
client_id: `${process.env.REACT_APP_CLIENT_ID}`,
client_secret: `${process.env.REACT_APP_CLIENT_SECRET}`,
ll: latlong,
radius: radius.toString(),
limit: "50",
};
const response = await fetchPlaces(new URLSearchParams(params).toString());
const { results } = response.data;
if (results.length !== 0) {
const closestPlaces = getClosestPlaces(results);
// AND HERE IS THE MAIN ISSUE! At this point all reqired data is ok it's an array of objects so I pass it to Action addPlaces which is addMany method.
dispatch(placesActions.addPlaces(closestPlaces));
} else if (results.length === 0 && radius < 1600) {
fetch(radius + 50, latlong, dispatch);
}
return [];
};
export { fetch };
And finally I want to show you Slice, where the method is stored. All the payloads are OK, but it doesn't work with updateMany ???
import {
createSlice,
EntityState,
createEntityAdapter,
} from "#reduxjs/toolkit";
import { FormattedPlace } from "./index";
import { RootState } from "./index";
import { Slice } from "#reduxjs/toolkit/src/createSlice";
import { SliceActions } from "#reduxjs/toolkit/dist/query/core/buildSlice";
const placesAdapter = createEntityAdapter<FormattedPlace>();
const initialState = placesAdapter.getInitialState();
type PlacesReducerActions = {
addPlaces(state: any, { payload }: { payload: any }): void;
};
export type PlacesSliceType = Slice<
EntityState<FormattedPlace>,
PlacesReducerActions,
"places"
>;
const placesSlice: PlacesSliceType = createSlice({
name: "places",
initialState,
reducers: {
addPlaces(state, { payload }) {
// HERE
placesAdapter.updateMany(state, payload);
},
},
});
export const selectors = placesAdapter.getSelectors<RootState>(
(state) => state.places
);
export const { actions } = placesSlice;
export default placesSlice.reducer;
Problem was solved with method setAll. I’m stupid, cause didn’t realise that method updateMany updates only those entities which had been added to state before. So if you want to rewrite your state totally use setAll()
I'm making a todo app and using useState to pass value to the form then submit the todo but for some reasons my todo form is not render and i don't know what is missing in my codes, please help me to check! Thank you so much!
import React, { useState } from "react";
function Todo({ todo, index }) {
console.log("hiiii");
return (
<div>
<p>{todo.text}</p>
</div>
);
}
function todoForm(addTodo) {
const [value, setValue] = useState("");
handleSubmit = (e) => {
e.preventDefault();
if (!value) return;
addTodo(value);
setValue("");
};
return (
<div>
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="add new todo"
value={value}
onChange={(e) => {
setValue(e.target.value);
}}
/>
</form>
</div>
);
}
function App() {
const [todos, setTodos] = useState([
{
text: "eat lunch",
isCompleted: false
},
{
text: "do homework",
isCompleted: false
},
{
text: "go to school",
isCompleted: false
}
]);
addTodo = (text) => {
console.log("hey");
const newTodos = [...todos, { text }];
setTodos(newTodos);
};
return (
<div>
<div>
{todos.map((todo, index) => {
return <Todo key={index} index={index} todo={todo} />;
})}
</div>
<div>
<todoForm addTodo={addTodo} />
</div>
</div>
);
}
export default App;
Link sandbox: https://codesandbox.io/s/serverless-bash-ef4hk?file=/src/App.js
JSX tags must be uppercased in order to be properly parsed by the compiler as a React component.
Instead of todoForm, use TodoForm.
Capitalized types indicate that the JSX tag is referring to a React component. These tags get compiled into a direct reference to the named variable, so if you use the JSX expression, Foo must be in scope.
From: https://reactjs.org/docs/jsx-in-depth.html#specifying-the-react-element-type
Also, you need to destructure props inside TodoForm in order to gain access to addTodo:
// Bad
function TodoForm(addTodo) {...}
// Good
function TodoForm({addTodo}) {...}
You should also assign you handlers to consts:
// Bad
addTodo = (text) => {...};
// Good
const addTodo = (text) => {...};
your problem is solved it
APP.JS
import React, { useState } from "react";
function Todo({ todo, index }) {
console.log("hiiii");
return (
<div>
<p>{todo.text}</p>
</div>
);
}
function todoForm(addTodo) {
const [value, setValue] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
if (!value) return;
addTodo(value);
setValue("");
};
return (
<div>
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="add new todo"
value={value}
onChange={(e) => {
setValue(e.target.value);
}}
/>
</form>
</div>
);
}
function App() {
const [todos, setTodos] = useState([
{
text: "eat lunch",
isCompleted: false
},
{
text: "do homework",
isCompleted: false
},
{
text: "go to school",
isCompleted: false
}
]);
const addTodo = (text) => {
console.log("hey");
const newTodos = [...todos, { text }];
setTodos(newTodos);
};
return (
<div>
<div>
{todos.map((todo, index) => {
return <Todo key={index} index={index} todo={todo} />;
})}
</div>
<div>
{todoForm(addTodo)}
</div>
</div>
);
}
export default App;
I facing a problem with using vue-slick carousel im my laravel project
I import it in main.js file import VueSlickCarousel from 'vue-slick-carousel'
Vue.use(VueSlickCarousel);
components: {
VueSlickCarousel
},
this is the component in my blade file
<VueSlickCarousel :arrows="true" :dots="true">
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
</VueSlickCarousel>
this is the error in the console
[Vue warn]: Unknown custom element: - did you register the component correctly? For recursive components, make sure to provide the "name" option.
please help me, Thanks in advance.
component code:
#push('scripts')
<script type="text/x-template" id="products-slider">
<div class="container" style="overflow:hidden;">
<div class="row">
<div class="col-12">
<h2 class="section-divider">
<span>
{{__('shop::app.home.new-products')}}
</span>
{{-- view all --}}
</h2>
<VueSlickCarousel :arrows="true" :dots="true">
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
</VueSlickCarousel>
{{-- <section class="regular slider">
#foreach (app('Webkul\Product\Repositories\ProductRepository')->getNewProducts() as $productFlat)
#include ('shop::products.list.old-card', ['product' => $productFlat])
#endforeach
</section> --}}
</div>
</div>
</div>
</script>
<script>
Vue.component('products-slider', {
import VueSlickCarousel from 'vue-slick-carousel',
template: '#products-slider',
components: {
VueSlickCarousel
},
data: function() {
return {
//products datat from API
products: [],
}
},
mounted: function () {
this.fetchProducts();
},
methods: {
// get products API
getProducts () {
return axios.get('{{url("/")}}/api/products?new=1&limit=6&order=desc&sort=created_at')
/* .then(response => {
const products = response.data.data;
this.products = products;
console.log(this.products);
}) */
/*try{
let prod = await axios.get('http://localhost/kshopnew/public/api/products');
const products = prod.data.data;
this.products = products;
}catch(e){
}*/
/*.then(response => {
this.products = response.data
})
.catch(e => {
this.errors.push(e)
})*/
},
// fetch products data
fetchProducts () {
this.getProducts ().then(response => {
const products = response.data.data;
this.products = products;
/* console.log(this.products); */
})
},
}
})
</script>
#endpush
Import vue-slick locally in your component and remove global registration
For example
<script>
import VueSlickCarousel from 'vue-slick-carousel'
export default {
components: {VueSlickCarousel}
}
</script>
In component code:
<script type="text/x-template" id="products-slider">
<div class="container" style="overflow:hidden;">
<div class="row">
<div class="col-12">
<h2 class="section-divider">
<span>
{{__('shop::app.home.new-products')}}
</span>
{{-- view all --}}
</h2>
<VueSlickCarousel :arrows="true" :dots="true">
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
</VueSlickCarousel>
{{-- <section class="regular slider">
#foreach (app('Webkul\Product\Repositories\ProductRepository')->getNewProducts() as $productFlat)
#include ('shop::products.list.old-card', ['product' => $productFlat])
#endforeach
</section> --}}
</div>
</div>
</div>
</script>
<script>
Vue.component('products-slider', {
template: '#products-slider',
data: function() {
return {
//products datat from API
products: [],
}
},
mounted: function () {
this.fetchProducts();
},
methods: {
// get products API
getProducts () {
return axios.get('{{url("/")}}/api/products?new=1&limit=6&order=desc&sort=created_at')
/* .then(response => {
const products = response.data.data;
this.products = products;
console.log(this.products);
}) */
/*try{
let prod = await axios.get('http://localhost/kshopnew/public/api/products');
const products = prod.data.data;
this.products = products;
}catch(e){
}*/
/*.then(response => {
this.products = response.data
})
.catch(e => {
this.errors.push(e)
})*/
},
// fetch products data
fetchProducts () {
this.getProducts ().then(response => {
const products = response.data.data;
this.products = products;
/* console.log(this.products); */
})
},
}
})
</script>
In main.js:
import VueSlickCarousel from 'vue-slick-carousel'
const app = new Vue({
el: '.element',
components: { VueSlickCarousel } // Note!!!
});
Remember to rebuild the front
my problem is fixed with :
Vue.component("VueSlickCarousel", require("vue-slick-carousel"));
instead of:
import VueSlickCarousel from 'vue-slick-carousel'
components: { VueSlickCarousel }
How to enable the circular progress when user clicks on submit on the login page? I can able to see the loader symbol in the app bar on other pages but I'm not able to activate it on the login page.
We need to add Custom reducer for login page. I did it in the following way.
1.1. Create a new login page. Just copy and paste the admin-on-rest login page code.
1.2. Update the propTypes like below
Login.propTypes = {
...propTypes,
authClient: PropTypes.func,
previousRoute: PropTypes.string,
theme: PropTypes.object.isRequired,
translate: PropTypes.func.isRequired,
userLogin: PropTypes.func.isRequired,
isLogging: PropTypes.bool.isRequired,
};
1.3. Add the below line
function mapStateToProps(state, props) {
return {
isLogging: state.loginReducer > 0
};
}
1.4. Update the login page with below code.
const enhance = compose(
translate,
reduxForm({
form: 'signIn',
validate: (values, props) => {
const errors = {};
const { translate } = props;
if (!values.username) errors.username = translate('aor.validation.required');
if (!values.password) errors.password = translate('aor.validation.required');
return errors;
},
}),
connect(mapStateToProps, { userLogin: userLoginAction }),
);
export default enhance(Login);
1.5. Replace the submit button code
<CardActions>
<RaisedButton type="submit" primary disabled={isLogging} icon={isLogging && <CircularProgress size={25} thickness={2} />} label={translate('aor.auth.sign_in')} fullWidth />
</CardActions>
1.6 The complete code for the login page is
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { propTypes, reduxForm, Field } from 'redux-form';
import { connect } from 'react-redux';
import compose from 'recompose/compose';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import { Card, CardActions } from 'material-ui/Card';
import Avatar from 'material-ui/Avatar';
import RaisedButton from 'material-ui/RaisedButton';
import TextField from 'material-ui/TextField';
import CircularProgress from 'material-ui/CircularProgress';
import { cyan500, pinkA200, white } from 'material-ui/styles/colors';
import defaultTheme, {translate, Notification, userLogin as userLoginAction } from 'admin-on-rest';
const styles = {
main: {
display: 'flex',
flexDirection: 'column',
minHeight: '100vh',
alignItems: 'center',
justifyContent: 'center',
},
card: {
minWidth: 300,
},
avatar: {
margin: '1em',
textAlign: 'center ',
},
avatarText:{
verticalAlign:'middle',
fontSize:20,
},
form: {
padding: '0 1em 1em 1em',
},
input: {
display: 'flex',
},
};
function getColorsFromTheme(theme) {
if (!theme) return { primary1Color: cyan500, accent1Color: pinkA200 };
const {
palette: {
primary1Color,
accent1Color,
},
} = theme;
return { primary1Color, accent1Color };
}
// see http://redux-form.com/6.4.3/examples/material-ui/
const renderInput = ({ meta: { touched, error } = {}, input: { ...inputProps }, ...props }) =>
<TextField
errorText={touched && error}
{...inputProps}
{...props}
fullWidth
/>;
class Login extends Component {
login = (auth) => this.props.userLogin(auth, this.props.location.state ? this.props.location.state.nextPathname : '/');
render() {
const { handleSubmit, submitting, theme, translate, isLogging } = this.props;
const muiTheme = getMuiTheme(theme);
const { primary1Color } = getColorsFromTheme(muiTheme);
return (
<MuiThemeProvider muiTheme={muiTheme}>
<div style={{ ...styles.main, backgroundColor: primary1Color }}>
<Card style={styles.card}>
<div style={styles.avatar}>
<div>
<Avatar backgroundColor={white} src="EnsembleGreenLogo.png" size={45} />
</div>
<div>
<span style={styles.avatarText}>Ensemble SmartWAN Manager</span>
</div>
</div>
<form onSubmit={handleSubmit(this.login)}>
<div style={styles.form}>
<div style={styles.input} >
<Field
name="username"
component={renderInput}
floatingLabelText={translate('aor.auth.username')}
disabled={submitting}
/>
</div>
<div style={styles.input}>
<Field
name="password"
component={renderInput}
floatingLabelText={translate('aor.auth.password')}
type="password"
disabled={submitting}
/>
</div>
</div>
<CardActions>
<RaisedButton
type="submit"
primary
disabled={isLogging}
icon={isLogging && <CircularProgress size={25} thickness={2} />}
label={translate('aor.auth.sign_in')}
fullWidth
/>
</CardActions>
</form>
</Card>
<Notification />
</div>
</MuiThemeProvider>
);
}
}
Login.propTypes = {
...propTypes,
authClient: PropTypes.func,
previousRoute: PropTypes.string,
theme: PropTypes.object.isRequired,
translate: PropTypes.func.isRequired,
userLogin: PropTypes.func.isRequired,
isLogging: PropTypes.bool.isRequired,
};
Login.defaultProps = {
theme: defaultTheme,
};
function mapStateToProps(state, props) {
return {
isLogging: state.loginReducer > 0
};
}
const enhance = compose(
translate,
reduxForm({
form: 'signIn',
validate: (values, props) => {
const errors = {};
const { translate } = props;
if (!values.username) errors.username = translate('aor.validation.required');
if (!values.password) errors.password = translate('aor.validation.required');
return errors;
},
}),
connect(mapStateToProps, { userLogin: userLoginAction }),
);
export default enhance(Login);
2.1. Add a new file (src/loginReducer.js) in src folder with the below content
import { USER_LOGIN_LOADING, USER_LOGIN_SUCCESS, USER_LOGIN_FAILURE, USER_CHECK } from 'admin-on-rest';
export default (previousState = 0, { type }) => {
switch (type) {
case USER_LOGIN_LOADING:
return previousState + 1;
case USER_LOGIN_SUCCESS:
case USER_LOGIN_FAILURE:
case USER_CHECK:
return Math.max(previousState - 1, 0);
default:
return previousState;
}
};
3.1 Update the app.js admin tag.
<Admin
menu={createMenus}
loginPage={Login}
dashboard={Dashboard}
appLayout={Layout}
customReducers={{ loginReducer }}
>
3.2 import the login page and login reducers in app.js
import loginReducer from './loginReducer';
import Login from "./Login";
I have a problem I can't solve trying to use redux-form. I'm trying the Erikras boilerplate. I want the form to be a component and the parent to call handleSubmit (for the moment with a console.log just to confirm it works). Here, the two:
import React, {Component, PropTypes} from 'react';
import Helmet from 'react-helmet';
import {initialize} from 'redux-form';
import {connect} from 'react-redux';
import * as membersActions from 'redux/modules/members';
import {isLoaded, loadMembers} from 'redux/modules/members';
import { DashboardList } from 'components';
import { DashboardHeader } from 'components';
import { DashboardAdding } from 'components';
import { asyncConnect } from 'redux-async-connect';
#asyncConnect([{
deferred: true,
promise: ({store: {dispatch, getState}}) => {
if (!isLoaded(getState())) {
return dispatch(loadMembers());
}
}
}])
#connect(
state => ({
members: state.members.data,
error: state.members.error,
loading: state.members.loading
}),
{...membersActions, initialize })
export default class Dashboard extends Component {
static propTypes = {
initialize: PropTypes.func.isRequired,
members: PropTypes.array,
loadMembers: PropTypes.func.isRequired
}
handleSubmit = (data) => {
console.log(data);
this.props.initialize('dashAdding', {});
}
handleInitialize = () => {
this.props.initialize('dashAdding', {
pseudo: 'Pibo',
email: 'pibirino#gmail.com'
});
}
render() {
const {members} = this.props;
return (
<div className="container">
<h1>Dashboard</h1>
<Helmet title="Dashboard"/>
<DashboardHeader />
<div>
<DashboardList members={members}/>
<h3>Ici commence le form</h3>
<div style={{textAlign: 'center', margin: 15}}>
<button className="btn btn-primary" onClick={this.handleInitialize}>
<i className="fa fa-pencil"/> Initialize Form
</button>
</div>
</div>
<DashboardAdding onSubmit={this.handleSubmit}/>
<p>Bleeeeah!!</p>
</div>
);
}
}
and here the child:
import React, {Component, PropTypes} from 'react';
import {reduxForm} from 'redux-form';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import memberValidation from './memberValidation';
#reduxForm({
form: 'dashAdding',
fields: ['pseudo', 'email'],
validate: memberValidation
})
export default class DashboardAdding extends Component {
static propTypes = {
fields: PropTypes.object.isRequired,
handleSubmit: PropTypes.func.isRequired,
resetForm: PropTypes.func.isRequired
}
render() {
const {
fields: { pseudo, email},
handleSubmit,
resetForm
} = this.props;
const renderInput = (field, label) =>
<div className={'form-group' + (field.error && field.touched ? ' has-error' : '')}>
<label htmlFor={field.name} className="col-sm-2">{label}</label>
<div className={'col-sm-8 '}>
<input type="text" className="form-control" id={field.name} {...field}/>
</div>
</div>;
return (
<div>
<form className="form-horizontal" onSubmit={handleSubmit}>
{renderInput(pseudo, 'Full Name')}
{renderInput(email, 'Email', true)}
<div className="form-group">
<div className="col-sm-offset-2 col-sm-10">
<button className="btn btn-success" onClick={handleSubmit}>
<i className="fa fa-paper-plane"/> Submit
</button>
<button className="btn btn-warning" onClick={resetForm} style={{marginLeft: 15}}>
<i className="fa fa-undo"/> Reset
</button>
</div>
</div>
</form>
</div>
);
}
}
So... it doesn't work I think I'm missing some important knowledge. I thought that the reason is because the form component is dumb, and it doesn't have the dispatch function. So, I tryed to add this (several times in several different ways) importing the action creator from the specific folder:
#connect(() => ({}),
dispatch => actionCreators( dispatch)
)
But I don't still get what I want. What's the problem?
So, finally I found the answer by myself. In fact, I decided to not use #connect, whitch is deprecated (despite it's still used in the boilerplate) and to use connect, as in the example of the redux-form documentation. The only change concerned the parent, but I'll post them both in case I missed something. The following code works good.
Here is the code:
import React, {Component, PropTypes} from 'react';
import Helmet from 'react-helmet';
import {initialize} from 'redux-form';
import {connect} from 'react-redux';
import * as membersActions from 'redux/modules/members';
import {isLoaded, loadMembers} from 'redux/modules/members';
import { DashboardList } from 'components';
import { DashboardHeader } from 'components';
import { DashboardAdding } from 'components';
import { asyncConnect } from 'redux-async-connect';
#asyncConnect([{
deferred: true,
promise: ({store: {dispatch, getState}}) => {
if (!isLoaded(getState())) {
return dispatch(loadMembers());
}
}
}])
class Dashboard extends Component {
static propTypes = {
members: PropTypes.array,
error: PropTypes.string,
loading: PropTypes.bool,
addMember: PropTypes.func,
initialize: PropTypes.func.isRequired,
newMemberData: PropTypes.object
}
handleSubmit = (data, dispatch) => {
dispatch(addMember(JSON.stringify(data)));
this.props.initialize('dashboardForm', {});
}
handleInitialize = () => {
this.props.initialize('dashboardForm', {
pseudo: 'Pibo',
email: 'pibirino#gmail.com'
});
}
render() {
const {members} = this.props;
return (
<div className="container">
<h1>Dashboard</h1>
<Helmet title="Dashboard"/>
<DashboardHeader />
<div>
<DashboardList members={members}/>
<h3>Ici commence le form</h3>
<div style={{textAlign: 'center', margin: 15}}>
<button className="btn btn-primary" onClick={this.handleInitialize}>
<i className="fa fa-pencil"/> Initialize Form
</button>
</div>
</div>
<DashboardAdding onSubmit={this.handleSubmit}/>
</div>
);
}
}
function mapStateToProps(state) {
return {
members: state.members.data,
error: state.members.error,
loading: state.members.loading,
newMemberData: state.addSingleMember.data
};
}
function matchDispatchToProps(dispatch) {
return bindActionCreators({
addActions,
initialize: initialize
}, dispatch);
}
export default connect(mapStateToProps, matchDispatchToProps)(Dashboard);
...and the child component:
import React, {Component, PropTypes} from 'react';
import {reduxForm} from 'redux-form';
import memberValidation from './memberValidation';
class DashboardAdding extends Component {
static propTypes = {
fields: PropTypes.object.isRequired,
handleSubmit: PropTypes.func.isRequired,
resetForm: PropTypes.func.isRequired
}
render() {
const {
fields: { pseudo, email},
handleSubmit,
resetForm
} = this.props;
const renderInput = (field, label) =>
<div className={'form-group' + (field.error && field.touched ? ' has-error' : '')}>
<label htmlFor={field.name} className="col-sm-2">{label}</label>
<div className={'col-sm-8 '}>
<input type="text" className="form-control" id={field.name} {...field}/>
{field.error && field.touched && <div className="text-danger">{field.error}</div>}
</div>
</div>;
return (
<div>
<form className="form-horizontal" onSubmit={handleSubmit.bind(this)}>
{renderInput(pseudo, 'Pseudo')}
{renderInput(email, 'Email', true)}
<div className="form-group">
<div className="col-sm-offset-2 col-sm-10">
<button className="btn btn-success" onClick={handleSubmit}>
<i className="fa fa-paper-plane"/> Submit
</button>
<button className="btn btn-warning" onClick={resetForm} style={{marginLeft: 15}}>
<i className="fa fa-undo"/> Reset
</button>
</div>
</div>
</form>
</div>
);
}
}
export default reduxForm({
form: 'dashboardForm',
fields: ['pseudo', 'email'],
validate: memberValidation,
asyncBlurFields: ['email']
})(DashboardAdding);
The addMember function contains obviously a promise.
I hope it will helps somebody :-)