I'm loading translations dynamically, with the HttpBackend
However inside my component I use the Trans component, and it is screaming about missing translation key, I also see the init finished after it tried to access the Trans component.
I have a Suspens around my app, why is this happens?
I get in the console:
i18next::translator: missingKey en translation myKey This is text that has a in it....
the init happens after.
How can I fix this?
// file: i18n.js
i18n
// load translation using http -> see /public/locales (i.e. https://github.com/i18next/react-i18next/tree/master/example/react/public/locales)
// learn more: https://github.com/i18next/i18next-http-backend
.use(Backend)
// detect user language
// learn more: https://github.com/i18next/i18next-browser-languageDetector
// pass the i18n instance to react-i18next.
.use(initReactI18next)
// init i18next
// for all options read: https://www.i18next.com/overview/configuration-options
.init({
fallbackLng: 'en',
debug: true,
supportedLngs: ['en', 'bg', 'rs'],
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
},
backend: {
loadPath: '/static/{{lng}}.translations.json',
},
react: {
wait: true,
useSuspense: true,
},
transSupportBasicHtmlNodes: true,
});
export default i18n;
// app.js
function App() {
return (
<BrowserRouter>
<Apollo>
<Suspense fallback={<Loading />}>
<ThemeProvider theme={theme}>
<Header />
<Routes />
<Footer />
</ThemeProvider>
</Suspense>
</Apollo>
</BrowserRouter>
);
}
problematic component:
//home
const I18N_TEXT_KEY_CONSTANT = 'text_key';
const Home = () => (
<Trans i18nKey={I18N_TEXT_KEY_CONSTANT}>
This is text that has a <br /> in it and also some random spaces.
</Trans>
);
You should pass the t function to Trans component.
//home
import { useTranslation } from 'react-i18next';
const I18N_TEXT_KEY_CONSTANT = 'text_key';
const Home = () => {
const { t } = useTranslation();
return (
<Trans i18nKey={I18N_TEXT_KEY_CONSTANT} t={t}>
This is text that has a <br /> in it and also some random spaces.
</Trans>
);
};
The solution which is sadly not document is:
const {t} = useTranstions()
<Trans i18nKey="someKey" t={t}>
Passing the t into the Trans works perfectly.
Related
I'm following the steps from this. And my CKEditor now can run on my nextjs app. But the problem is when I wanna put simpleUploadAdapter, there is an error message saying props.editor.create is not a function. Here's the code :
import Head from 'next/head'
import styles from '../styles/Home.module.css'
import React, { useState, useEffect, useRef } from 'react'
export default function Home() {
const editorCKRef = useRef()
const [editorLoaded, setEditorLoaded] = useState(false)
const { CKEditor, SimpleUploadAdapter, ClassicEditor } = editorCKRef.current || {}
useEffect(() => {
editorCKRef.current = {
CKEditor: require('#ckeditor/ckeditor5-react'),
// SimpleUploadAdapter: require('#ckeditor/ckeditor5-upload/src/adapters/simpleuploadadapter'),
ClassicEditor: require('#ckeditor/ckeditor5-build-classic')
}
setEditorLoaded(true)
}, [])
return (
<div className={styles.container}>
<Head>
<title>My CKEditor 5</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<h2>Using CKEditor 5 build in Next JS</h2>
{editorLoaded && ClassicEditor &&
<CKEditor
name="editor"
editor={ typeof ClassicEditor !== 'undefined' ?
ClassicEditor.create(
document.getElementsByName("editor"), {
plugins: [ SimpleUploadAdapter],
//toolbar: [ ... ],
simpleUpload: {
// The URL that the images are uploaded to.
uploadUrl: 'http://example.com',
// Enable the XMLHttpRequest.withCredentials property.
withCredentials: false
}
}
): ''
}
data="<p>Hello from CKEditor 5!</p>"
onInit={ editor => {
// You can store the "editor" and use when it is needed.
console.log( 'Editor is ready to use!', editor );
} }
onChange={ ( event, editor ) => {
const data = editor.getData();
console.log('ON CHANGE')
// console.log(ClassicEditor.create())
// console.log( { event, editor, data } );
} }
onBlur={ ( event, editor ) => {
console.log( 'Blur.', editor );
} }
onFocus={ ( event, editor ) => {
console.log( 'Focus.', editor );
} }
config={
{
simpleUpload: {
uploadUrl: 'localhost:8000/api/files/upload/question/1'
}
}
}
/>
}
</div>
)
}
and this is the error:
So what's the problem in here? Thank you
I got mine to work by wrapping the CKEditor component in a class component of my own.
class RichTextEditor extends React.Component<Props, State> {
render() {
const { content } = this.props;
return (
<CKEditor
editor={ClassicEditor}
data={content}
/>
);
}
}
It seems CKEditor just doesn't play nice with function components. Then use dynamic import to load the wrapper if you're using NextJS.
const RichTextEditor = dynamic(() => import("/path/to/RichTextEditor"), {
ssr: false,
});
I remembered that CKEditor4 is easier to setup in Next.js.
CKEditor5 require more work, you have to use dynamic import with mode ssr=false
But in your case, you also want to use another plugin SimpleUploadAdapter
I tried using CKEditor React component + build classic + SimpleUploadAdapter but meets the error "Code duplication between build classic and source (SimpleUploadAdapter)".
So I decided to custom the ckeditor5-build-classic, add the plugin into there and rebuild, then make it works :)(https://ckeditor.com/docs/ckeditor5/latest/builds/guides/development/custom-builds.html)
Here are few remarks:
Custom ckeditor5-build-classic
// ckeditor5-build-classic-custom local package
// Add SimpleUploadAdapter into the plugin list
// src/ckeditor.js
import SimpleUploadAdapter from '#ckeditor/ckeditor5-upload/src/adapters/simpleuploadadapter';
ClassicEditor.builtinPlugins = [
...
SimpleUploadAdapter
...
]
// Rebuild for using in our app
npm run build
Use the custom build in our app
// app/components/Editor.js
import CKEditor from "#ckeditor/ckeditor5-react";
import ClassicEditor from "#ckeditor/ckeditor5-build-classic";
...
<CKEditor
editor={ClassicEditor}
config={{
// Pass the config for SimpleUploadAdapter
// https://ckeditor.com/docs/ckeditor5/latest/features/image-upload/simple-upload-adapter.html
simpleUpload: {
// The URL that the images are uploaded to.
uploadUrl: "http://example.com",
// Enable the XMLHttpRequest.withCredentials property.
withCredentials: true,
// Headers sent along with the XMLHttpRequest to the upload server.
headers: {
"X-CSRF-TOKEN": "CSRF-Token",
Authorization: "Bearer <JSON Web Token>",
},
},
}}
...
Dynamic import for loading the editor from client-side
// pages/index.js
import dynamic from "next/dynamic";
const Editor = dynamic(() => import("../components/editor"), {ssr: false})
To sum up:
Custom the CKEditor build, add needed plugins... then rebuild. Make them as a local package
Use that local package in our app!
Check my git sample with long comments: https://github.com/nghiaht/nextjs-ckeditor5
Hello i'm trying to load a style into my ui stuff but i'm having trouble doing that
const useStyles = makeStyles(loginPage)
const classes = useStyles();
const renderTextField = ({
label,
input,
meta: { touched, invalid, error },
...custom
}) => (
<TextField
label={label}
placeholder={label}
variant="outlined"
InputLabelProps={{
classes: {
root: classes.label,
focused: classes.focusedLabel,
error: classes.erroredLabel
}
}}
InputProps={{
classes: {
root: classes.cssOutlinedInput,
focused: classes.cssFocused,
notchedOutline: classes.notchedOutline,
},
startAdornment: (
<InputAdornment position="start">
<PersonSharpIcon style={{ fontSize: 25 , color: 'rgba(20, 176, 12,0.9)' }} />
</InputAdornment>
)
}}
error={touched && invalid}
helperText={touched && error}
{...input}
{...custom}
/>
)
error:
Error: Invalid hook call. Hooks can only be called inside of the body
of a function component.
Could someone help me how I would solve this?
It's exactly as the error message says. You need to move your hook inside the body of your function component.
React considers every function starting with 'use' as a hook. So in your case it's useStyles(). React also expects such functions to be called only from inside of the body of function components and only from the root of it (so nesting it inside loops or conditional statements is a big no - you can read about it here). Your function component is renderTextField, so as you can see you're calling useStyles() OUTSIDE of renderTextField's body.
Structuring it something like this should help:
const useStyles = makeStyles(loginPage);
const RenderTextField = ({
label,
input,
meta: { touched, invalid, error },
...custom
}) => {
const classes = useStyles(); // <-- Move it here
return (
<TextField
label={label}
...
>
...
</TextField>
);
}
I have written code, which uses a Modal dialog to display a form.
My react app is rendered at "root"
index.html
<div id="root"></div>
App.js
const store = configureStore();
ReactDOM.render(
<Provider store={store}>
<ExampleBasic/>
</Provider>
, document.getElementById('root'));
ExmpleBasic.js
Please ignore state management in component here. this is just for example.
import React, { PureComponent } from 'react';
import Lorem from 'react-lorem-component';
import Modal from '#atlaskit/modal-dialog';
import Button from '#atlaskit/button';
export default class ExampleBasic extends PureComponent {
state = { isOpen: false }
open = () => this.setState({ isOpen: true })
close = () => this.setState({ isOpen: false })
secondaryAction = ({ target }) => console.log(target.innerText)
render() {
const { isOpen } = this.state;
const actions = [
{ text: 'Close', onClick: this.close },
{ text: 'Secondary Action', onClick: this.secondaryAction },
];
return (
<div>
<Button onClick={this.open}>Open Modal</Button>
{isOpen && (
<Modal
actions={actions}
onClose={this.close}
heading="Modal Title"
>
<BasicFormContainer />
</Modal>
)}
</div>
);
}
}
BasicFormContainer.js
const mapStateToProps = state => ({
addDesignation: state.designations.addDesignation,
});
const mapDispatchToProps = dispatch => ({
});
export default connect(mapStateToProps, mapDispatchToProps)(BasicForm);
BasicForm.js
import React, { Component } from 'react';
import { Field, reduxForm } from 'redux-form';
class BasicForm extends Component {
constructor(props) {
super(props);
this.submit = this.submit.bind(this);
}
submit(values) {
console.log(values);
}
render() {
const { handleSubmit } = this.props;
return (
<form onSubmit={handleSubmit(this.submit)}>
<Field
name="designationName"
component="input"
placeholder="Name"
label="Enter name"
autoFocus
/>
</form>
);
}
}
export default reduxForm({
form: 'BasicForm',
enableReinitialize: true,
})(BasicForm);
However modal is rendered using portal, outside current DOM.
As modal is rendered outside the scope of redux context, it is not getting the
store. and i am getting an error "Uncaught Error: Field must be inside a component decorated with reduxForm()"
Below is link to same kind of problem, where redux form within portal is not working.
Redux Form Wrapped Inside Custom Portal Component?
in React 16 it is handled by portals, but version before then that you can try something like as follow.
export default class ExampleBasic extends PureComponent {
...
static contextTypes = { store: React.PropTypes.object };
render() {
const { isOpen } = this.state;
const actions = [
{ text: 'Close', onClick: this.close },
{ text: 'Secondary Action', onClick: this.secondaryAction },
];
return (
<div>
<Button onClick={this.open}>Open Modal</Button>
{isOpen && (
<Modal
actions={actions}
onClose={this.close}
heading="Modal Title"
>
<Provider store={this.context.store}>
<BasicFormContainer />
</Provider>
</Modal>
)}
</div>
);
}
}
You need to pass in the values of BasicForm.js to the Redux store and dispatch an action from there itself and not from the BasicFormContainer.js. This way, the Modal remains inside of the scope of your root element and thus there is no need to access the store outside of the Provider.
Then update the Redux store based on the values entered in the form. Once, the store is updated, you can then access it from anywhere in your application such as Modal in your case.
I downgraded to version 2.1.0 to solve the problem.
Have to create an edit Page editing a number of parameters on an instance of a'tale' resource.
However adding any element such as an MUI Card or even a div, is causing the app to freeze in various ways.
These are the approaches I have tried.
1) Adding a card component or placing my elements within a div for styling
export const EditorEditTale = (props) => {
return (
<Edit {...props} title="Tale Editor">
<SimpleForm >
<div>
<Image />
<TaleCardHeader props={ props } style={taleCardHeaderStyle.editor} />
</div>
</SimpleForm>
</Edit>
)
};
This is causing nothing to render.
Second approach, assuming that the record and basePath arent getting propagated to the children completely. Trying to use component like below.
const Input = ({record, basePath}) => {
return (
<div>
<LongTextInput source="taleText" />
</div>
)
}
This is causing the page to not render with everything in some kind of locking loop with the error - cannot read property touched of undefined.
How should I create a custom Edit page with a complex inputs and styling.
UPDATE: Been trying to write a custom form to substitute the SimpleForm component with no luck so far.
To create a custom form you can follow these steps:
make an exact copy of SimpleForm to your project.
rename SimpleForm to what you want.
fix all the relative imports.
test the new form until it works.
I made a minimum working form based on current master branch's SimpleForm
import React, { Children, Component } from 'react';
import PropTypes from 'prop-types';
import { reduxForm, Field } from 'redux-form';
import { connect } from 'react-redux';
import compose from 'recompose/compose';
import getDefaultValues from 'admin-on-rest/mui/form/getDefaultValues';
import FormField from 'admin-on-rest/mui/form/FormField';
import Toolbar from 'admin-on-rest/mui/form/Toolbar';
const formStyle = { padding: '0 1em 1em 1em' };
export class PostForm extends Component {
handleSubmitWithRedirect = (redirect = this.props.redirect) => this.props.handleSubmit(values => this.props.save(values, redirect));
render() {
const { children, invalid, record, resource, basePath, submitOnEnter, toolbar } = this.props;
return (
<form className="simple-form">
<Field name="name_of_a_field" component="input" />
{toolbar && React.cloneElement(toolbar, {
handleSubmitWithRedirect: this.handleSubmitWithRedirect,
invalid,
submitOnEnter,
})}
</form>
);
}
}
PostForm.propTypes = {
basePath: PropTypes.string,
children: PropTypes.node,
defaultValue: PropTypes.oneOfType([
PropTypes.object,
PropTypes.func,
]),
handleSubmit: PropTypes.func, // passed by redux-form
invalid: PropTypes.bool,
record: PropTypes.object,
resource: PropTypes.string,
redirect: PropTypes.oneOfType([
PropTypes.string,
PropTypes.bool,
]),
save: PropTypes.func, // the handler defined in the parent, which triggers the REST submission
submitOnEnter: PropTypes.bool,
toolbar: PropTypes.element,
validate: PropTypes.func,
};
PostForm.defaultProps = {
submitOnEnter: true,
toolbar: <Toolbar />,
};
const enhance = compose(
connect((state, props) => ({
initialValues: getDefaultValues(state, props),
})),
reduxForm({
form: 'record-form',
enableReinitialize: true,
}),
);
export default enhance(PostForm);
The above code works for AOR's example.
I hope this helps.
(import might be slightly different when you have AOR as npm dependency :
import getDefaultValues from 'admin-on-rest/lib/mui/form/getDefaultValues';
import FormField from 'admin-on-rest/lib/mui/form/FormField';
import Toolbar from 'admin-on-rest/lib/mui/form/Toolbar';
)
Documenting my final answer. You have to create a custom Redux Form. You can use AOR Input components straight. They come prewrapped for Redux Form.
import { Field, reduxForm } from 'redux-form';
import compose from 'recompose/compose';
import { connect } from 'react-redux';
class StyledForm extends Component {
// Newer version of aor needs this function defined and passed to save buttons. All props are being passed by parent List component.
handleSubmitWithRedirect = (redirect = this.props.redirect) => this.props.handleSubmit(values => this.props.save(values, redirect));
render() {
const { handleSubmit, invalid, record, resource, basePath } = this.props
return (<div>
<form onSubmit={handleSubmit} >
<Card >
<CardText >
//This component simply displays data, something not possible very easily with SimpleForm.
<HeaderComp basePath={basePath} record={record} />
<Field source="category_id"
optionText="categoryName"
reference="categories"
resource={resource}
record={record}
basePath={basePath}
name="NAME OF THE FIELD IN YOUR REDUX DATASTORE"
component={REFERENCEFIELDCOMP} />
//create complex div structures now.
<div >
<span>Tale</span>
<Field resource={resource} record={record} basePath={basePath} name="taleText" component={TextInput} />
</div>
</CardText >
<MuiToolbar>
<ToolbarGroup>
<SaveButton handleSubmitWithRedirect={this.handleSubmitWithRedirect}/>
//Add custom buttons with custom actions
<Field record={record} name="status" component={EditButtons} />
</ToolbarGroup>
</MuiToolbar>
</Card>
</form>
</div>)
}
};
const enhance = compose(
connect((state, props) => ({
initialValues: getDefaultValues(state, props),
})),
reduxForm({
form: 'record-form',
enableReinitialize: true,
}),
);
export default enhance(StyledForm);
You will have to either import or copy getDefaultValues from AOR in the node modules.
I copied it into the file below.
import getDefaultValues from '../functions/getDefaultValues';
If you need a referenceField in your field. Then wrap it in a custom component like shown below
const DropDownSelector = ({optionText, ...props}) => {
return (
<ReferenceInput {...props} label="" >
<SelectInput optionText={optionText} />
</ReferenceInput>
)
}
I am fetching data from a remote API in componentDidMount:
componentDidMount() {
this.props.fetchRemoteData('photos')
}
And then the received data is passed to my component props in mapStateToProps, using a selector to filter a specific object from the received array:
const mapStateToProps = (state, { params }) => {
const photoId = parseInt(params.photoId)
return {
singlePhoto: getSinglePhoto(state.filteredList.photos.jsonArray, photoId),
isFetching: state.filteredList.photos.isFetching
}
}
The content renders, but there is a split second before that, where it seems to be trying to the render the content before the data is successfully retrieved, which brings up the following error in the console:
Uncaught TypeError: Cannot read property 'charAt' of undefined
undefined is here referring to this.props.singlePhoto. But when singlePhoto receives the data payload the content renders.
Here is my container component:
class PhotoSingle extends Component {
componentDidMount() {
this.props.fetchRemoteData('photos')
}
render() {
const {singlePhoto, isFetching} = this.props
const photoTitle = capitalizeFirstLetter(singlePhoto.title)
return (
<div>
<PhotoSingleImg singlePhoto={singlePhoto} photoTitle={photoTitle} isFetching={isFetching}/>
</div>
)
}
}
const mapStateToProps = (state, { params }) => {
const photoId = parseInt(params.photoId)
return {
singlePhoto: getSinglePhoto(state.filteredList.photos.jsonArray, photoId),
isFetching: state.filteredList.photos.isFetching
}
}
import * as actions from '../actions/actionCreators'
PhotoSingle = connect(mapStateToProps, actions)(PhotoSingle)
export default PhotoSingle;
And my presentational component:
const PhotoSingleImg = ({ singlePhoto, photoTitle, isFetching }) => {
if (isFetching) {
return <h4>Fetching data...</h4>
}
return (
<div>
<h1>Single Photo</h1>
<h3>Title</h3>
<hr />
<img className='single-photo' src={singlePhoto.url} />
<p>Album ID: {singlePhoto.albumId} | Photo ID: {singlePhoto.id}</p>
</div>
)
}
export default PhotoSingleImg;
I'm unsure how to make it so the content will only attempt to render after I the API response has been received.
Any help appreciated.
Have you defined initial state in redux store?
You can try this way:
return singlePhoto ?
(<div>
<h1>Single Photo</h1>
<h3>Title</h3>
<hr />
<img className='single-photo' src={singlePhoto.url} />
<p>Album ID: {singlePhoto.albumId} | Photo ID: {singlePhoto.id}</p>
</div>) : null